*/
+ $"A177 C3EA 4FE4 AFFE 1BFF 61D3 2F87 4BFC" /* wO.a/K */
+ $"946E 03F8 6CFF 06A3 FC28 FE13 BF0F 6B7E" /* n.l.(..k~ */
+ $"1EB8 FC3B 47E1 ECEE EEDF F87D 51FF 07D5" /* .;G}Q. */
+ $"0BBB 87E1 D3FF 0779 FC3B 6FE1 D378 0DF0" /* ..y;ox. */
+ $"BD7C 29EB A2DD 17F2 6ADB E1DB 7F0D 97E1" /* |).j.. */
+ $"E84D 7750 FC3E 906F C3E8 35DD 42ED AFE1" /* MwP>o5B */
+ $"D33F 0E8D F86E 00F7 91DD 3D8B A94D D83C" /* ?.n.=M< */
+ $"B51B 129C 103F 899D B929 4724 C188 2C29" /* ...?)G$,) */
+ $"CE89 11F8 9065 1C37 E234 DFEF 60D7 3221" /* Ή.e.74`2! */
+ $"5343 F889 DCDB AFB3 6F20 F76A 31D9 986E" /* SCۯo j1٘n */
+ $"A745 547E 6F18 076D A675 BA1A 1AAF 6558" /* ET~o..mu..eX */
+ $"338A FF71 4707 0776 1B62 BCCC 89E5 4726" /* 3qG..v.b̉G& */
+ $"A7CB 8885 E07C DADC D741 3B1D D81F AB03" /* ˈ|A;... */
+ $"2F84 F52D DFD5 7384 4649 7BA2 5AB1 14C8" /* /-sFI{Z. */
+ $"3D8D B951 8183 C33A B08E 82CC EF1A F046" /* =Q:.F */
+ $"5B62 C9E0 A926 F330 0A61 A30C 6194 7E1E" /* [b&0a.a~. */
+ $"1EE9 3D0D 1ABE 5536 A026 8B32 830F C02F" /* .=..U6&2./ */
+ $"3041 04B7 2C65 024D AC81 D2CC 03CA A08A" /* 0A.,e.M.ʠ */
+ $"B105 F3F1 C365 6BCE 9876 A867 7DBA 63AF" /* .ekΘvg}c */
+ $"0973 495F DCC1 3552 54BA 5BDB 618D C6C0" /* sI_5RT[a */
+ $"91B8 4489 141B A7D3 F188 8A97 DAB0 8942" /* D..ڰB */
+ $"EA67 F235 E18A 871B 4B0C DAC6 9050 BA9C" /* g5ኇ.K.ƐP */
+ $"C432 95CE 13CA 93ED 4FD5 9495 377D C920" /* 2.ʓOՔ7} */
+ $"C8A2 F997 5348 8890 91D5 0A23 50BF 3C37" /* ȢSH#P<7 */
+ $"8372 7C96 32AA 1CD2 46BE 8220 C983 2A71" /* r|2.F Ƀ*q */
+ $"7961 BF33 9590 C086 CCE9 578B 322E FB50" /* ya3W2.P */
+ $"CB68 6459 C127 EB9B E974 EF02 59F4 FF4D" /* hdY't.YM */
+ $"4525 1874 6A9F 99EB DFCC C1B1 72EA 9FA0" /* E%.tjr */
+ $"DE00 BBB9 D95F 36B9 406E 3CE0 3BCF 32B6" /* ._6@n<;2 */
+ $"8DC0 55D6 C6DA 3622 8A6E 63F6 28AE 08AC" /* U6"nc(. */
+ $"03AC 288D 069A 750E CBBD 97D9 E42D BA93" /* .(.u.˽- */
+ $"E82A 4EB3 C008 CC7C B6A7 6F70 F1BC FDAA" /* *N.|op */
+ $"1DF4 D2E4 3ECB 9702 444E 278F 6B50 F00E" /* .>˗.DN'kP. */
+ $"C228 C132 E5E0 B4DE 93BF DBBD 2C85 ABF9" /* (2ޓ۽, */
+ $"7FAD EC28 1D40 D390 5A2F 9B14 D348 8C76" /* .(.@ӐZ/.Hv */
+ $"C04B 1173 BF19 8F50 7E3D 8311 BB79 FDA8" /* K.s.P~=.y */
+ $"1FD4 24A9 5F59 8B76 709E 5C84 1902 8690" /* .$_Yvp\.. */
+ $"AD49 334F 5AE8 E376 288C BDE7 EE13 FBDE" /* I3OZv(. */
+ $"7606 0135 6DB2 DCD9 F5B3 E7E2 B2B8 8BE1" /* v..5mⲸ */
+ $"1985 85D2 0EC1 5CCD BC74 D88B 7206 5075" /* ..\ͼt؋r.Pu */
+ $"FB92 BF92 C5CB B6E3 024D EB71 D909 AF79" /* ˶.MqƯy */
+ $"DD82 BBF2 37F1 1193 74D0 373D 4043 C292" /* ݂7.t7=@C */
+ $"0B1D 4E8F 04F4 A035 451A 7DCB 02DB D77E" /* ..N.5E.}.~ */
+ $"DD1C 3667 1BA7 2584 C128 3835 4454 5C15" /* .6g.%(85DT\. */
+ $"5846 C7D1 4689 3D2A 1172 9BA8 9830 F037" /* XFF=*.r07 */
+ $"0053 8FD6 4023 68C6 7122 CAC6 0325 5AF3" /* .S@#hq".%Z */
+ $"E104 A423 C5AE F551 6DF6 7B11 D5EB FD34" /* .#ŮQm{.4 */
+ $"DF67 C50C 737D 74D5 D5EC 7027 A2D8 C79A" /* g.s}tp'ǚ */
+ $"200D A972 A30A 509A C3DE 483F 8B53 D69C" /* .rPH?S֜ */
+ $"001F 47E9 BD2C 84EC DB83 B493 0326 DE6E" /* ..G,ۃ.&n */
+ $"336A 7C76 3225 8351 7B2D 37B5 D645 9866" /* 3j|v2%Q{-7Ef */
+ $"019E 6472 40C4 5853 3376 9D90 D3EA A727" /* .dr@XS3v' */
+ $"07A1 A4B8 A32F 475A 22A2 059C AB5B F13E" /* ./GZ".[> */
+ $"E2DF 5B3E E6B5 B27F 28AF A15F 400C 7D9F" /* [>浲.(_@.} */
+ $"43ED 33D4 9B4D CB64 E550 5FED FC2F E485" /* C3ԛMdP_/ */
+ $"271E 2DB1 6AE5 6793 EFE8 FCC5 A784 4C95" /* '.-jgŧL */
+ $"E488 B5AC A528 BACB 10FF 4ABD D554 E56B" /* 䈵(.JTk */
+ $"F72E 8ED7 5E48 60A4 82D0 381F A2D4 625C" /* .^H`8.b\ */
+ $"9F80 2A63 5FD3 DD0C EDA8 3DE3 281E A65C" /* *c_.=(.\ */
+ $"88EA 1A96 626D DC03 79BE C4ED EA60 4FC5" /* .bm.y`O */
+ $"BB10 D9D5 99AF 0534 65C2 05AE 8D23 82B0" /* .ՙ.4e.# */
+ $"6FB7 881A 7BC5 0316 7A28 4B27 2763 A6C3" /* o.{..z(K''c */
+ $"8FC7 073A 1391 81BB 707F 625D 355F B1DF" /* .:.p.b]5_ */
+ $"A749 C3EE A157 EC1F 0F6C 70AB 1A36 5711" /* IW..lp.6W. */
+ $"C49B 1CA8 54A0 0FB9 6A2E 0F86 2E57 BC2F" /* ě.T.j...W/ */
+ $"8CDD B76A F6DB 83F9 EEFF 4BF1 DE60 660E" /* ݷjۃK`f. */
+ $"BCBA 7D68 D5DE 8663 DF06 3F65 A981 38D7" /* }hކc.?e8 */
+ $"CC20 EBAE 6143 C79E D2F3 DCF4 FA0F 433E" /* aCǞ.C> */
+ $"6DFE 4C5F 032A F6DD DD11 F85F 5003 22CD" /* mL_.*._P." */
+ $"C07E F1CC 40B9 8D61 825D 77C7 91A6 BD49" /* ~@a]wǑI */
+ $"2590 4BCD EB13 37A2 D5FA 2931 F311 9028" /* %K.7)1.( */
+ $"6C02 96FC ACF5 1EA4 8E51 4BC9 FDE8 DFEF" /* l..QK */
+ $"C5A0 8082 7E0D C127 3A80 A65B 7989 CCEE" /* Š~.':[y */
+ $"6F6A 8DDF FB5B 46A5 3757 7FC3 3EFD BC0A" /* oj[F7W.> */
+ $"8567 1CB0 3BE7 A153 D7CE 8F63 D2DC 8840" /* g.;SΏc܈@ */
+ $"D993 E028 0E86 AA99 3ADE CC55 A789 CFC8" /* ٓ(.:U */
+ $"D92B 9613 3850 B7B3 467A FA09 7D45 E174" /* +.8PFz}Et */
+ $"D351 3254 64A3 7FC5 0D8D 1EDB FEC9 3F05" /* Q2Td...?. */
+ $"C3C2 264B ACB8 F3C3 1BFF 04A5 12CF C191" /* &K... */
+ $"8CA0 5985 E34C E583 A230 CD43 8602 BAAF" /* YL僢0C. */
+ $"1475 FCA7 B274 1314 051F AF06 089D 13B1" /* .ut....... */
+ $"5C06 815E FAA5 6864 CECE D56A 127C 058B" /* \.^hdj.|. */
+ $"1859 E9E8 7A98 4299 3CFF 70CE C2DD 8676" /* .YzBQ.GN.=} */
+ $"818F 28A2 2AC0 AD00 7F03 5781 2882 A44A" /* (*...W(J */
+ $"6728 D9F6 E77F A883 C794 71AC 55E9 8112" /* g(.ǔqU. */
+ $"BF6F ECDA 9BD1 96B8 79AA A397 A6F2 C93F" /* oڛіy? */
+ $"3792 B8C4 8667 AAF4 659D 59F6 E77F A870" /* 7ĆgeY.p */
+ $"24F1 5461 DB69 0F4C F8B3 F02A F6E7 7FA6" /* $Tai.L*. */
+ $"26C9 5EDE 9BB6 A1C6 9467 854A 5327 BA38" /* &^ޛƔgJS'8 */
+ $"F535 A01A B90A 9580 95A0 FE5C A623 DE0D" /* 5.\#. */
+ $"6C3E BF23 66F2 9312 9490 EB88 1D63 7AF4" /* l>#f..cz */
+ $"9F5B 7F45 9078 3A5F CC36 7702 EAD5 FA4D" /* [.Ex:_6w.M */
+ $"5F9D 258B 14A8 A235 6490 6B7D 2B05 42C3" /* _%.5dk}+.B */
+ $"2A5A 791E 92C3 B1E8 B323 FF74 81FC B063" /* *Zy.ñ#tc */
+ $"4A31 CBBC 91E2 A64A F935 FE79 4C07 1376" /* J1˼J5yL..v */
+ $"002A 4795 5221 70B2 005C 41F9 D631 126F" /* .*GR!p.\A1.o */
+ $"FF7F DF03 06BD 7214 71AB 4EF3 8EEA 0261" /* ...r.qN.a */
+ $"D054 C087 EA15 CBD2 4645 2F9F 3600 4144" /* T.FE/6.AD */
+ $"5671 C61E D3CB 8B84 A60D B58E FB69 C71F" /* Vq.ˋ.i. */
+ $"B09A EDD9 AAC0 6FBA D7FE 8C4D 8262 6C73" /* ٪oMbls */
+ $"9F69 0E81 7581 1D52 FB27 C979 1597 69EA" /* i.u.R'y.i */
+ $"F1DF E906 0B77 2E21 D31A 5CCD 9474 95A2" /* ..w.!.\͔t */
+ $"CB48 AB27 5F53 B82F 1638 16A4 87CD ED1F" /* H'_S/.8.. */
+ $"20BC 2894 5EE1 C84C 1AC1 1158 0293 024E" /* (^L..X..N */
+ $"1023 0E4F 9BD8 FA73 6E62 FF7F FD08 5797" /* .#.Osnb..W */
+ $"0788 A951 6DDA 2448 21D2 7FCA 20B4 E1BF" /* .Qm$H!. */
+ $"3262 89C7 5386 ACB1 8B89 6AC3 F907 1355" /* 2bSj..U */
+ $"66D9 1E5A 5B54 19E9 2737 FF7C B8DF 1F5E" /* f.Z[T.'7|.^ */
+ $"3592 B16D 6089 DE73 8F19 479D 626B 977D" /* 5m`s.Gbk} */
+ $"185E F7A3 AF62 AD92 E5AC 2F74 B991 F96F" /* .^b/to */
+ $"211F 5AE0 4C27 344F A9E1 C731 DB20 A0FF" /* !.ZL'4O1 */
+ $"7FFF 7F2E 356F CC55 C6B8 C2C9 2AEF 2D31" /* ...5oUƸ*-1 */
+ $"EFEB D878 C16F B1B1 3D47 240C 489C 7153" /* xo=G$.HqS */
+ $"E5F8 A5C8 6E2C 7650 AED0 C4B7 43EA 67B6" /* n,vPķCg */
+ $"94E4 7274 CFEB 4CF3 FD3E 8065 B9CC 35A5" /* rtL>e5 */
+ $"21B1 3804 AD98 4F8A 6FFC 50DA 014C 442B" /* !8.OoP.LD+ */
+ $"AA41 1455 BD94 B366 A37D CA82 7FFC DE57" /* A.Uf}ʂ.W */
+ $"2EA9 98C2 5510 E51E 24CC 0428 4C83 1BBE" /* .U..$.(L. */
+ $"0A46 A6DD 6963 D4CB 24E8 748B ED10 7B6D" /* Fic$t.{m */
+ $"E61C 86C3 C80E 3030 FCE9 2263 C956 95A1" /* ..00"cV */
+ $"AED0 C4B1 D07C F701 9DA0 ED2C C401 BC1A" /* ı|.,.. */
+ $"9421 2D43 EF12 7494 8D46 CB82 3FE2 6439" /* !-C.tF˂?d9 */
+ $"6BDE C8CE 23B7 9D2E B6F9 DF4B C1A9 A145" /* k#.KE */
+ $"1512 85E6 30E6 98D1 AC22 EEBC 459C D841" /* ..0Ѭ"EA */
+ $"0A0F 1F31 2F75 7937 220F 7271 FE79 44A6" /* ..1/uy7".rqyD */
+ $"777E 1645 E0BA D980 0FFF 7D9F EC83 A930" /* w~.Eـ.}샩0 */
+ $"D12B 216D 4D3F 4AD0 5EE9 DBC7 53D7 0A21" /* +!mM?J^S! */
+ $"2372 C608 6701 8AFA 8E33 4125 2B7E 38DA" /* #r.g.3A%+~8 */
+ $"5990 99EE B1D0 B07C 9F9D 97DD D658 2C2A" /* Yа|X,* */
+ $"C7D6 4556 2AA7 297F ACBE 4802 B472 E76F" /* EV*).H.ro */
+ $"644F 2F11 17FF 7F9D CE1C 2989 7199 03B1" /* dO/....)q. */
+ $"E719 2B3B 1131 C4CC 808B BC7A 26A4 479F" /* .+;.1̀z&G */
+ $"4824 6486 E338 DB65 A502 454C C415 B8D0" /* H$d8e.EL. */
+ $"7C77 7323 E381 5958 F415 95B9 142E 85F8" /* |ws#YX... */
+ $"1742 E22F FF17 567B A195 DFF1 7EE0 8E40" /* .B/.V{~@ */
+ $"431A B629 DDCE AED1 A925 9FCD 56B5 7945" /* C.)ήѩ%VyE */
+ $"B8B9 77DF 732C CD1E 8137 2645 1135 9593" /* ws,.7&E.5 */
+ $"E31C 333B 30E2 518D E202 A1E0 FEA6 C40E" /* .3;0Q.. */
+ $"BB50 0174 C21D 0BFB 34C1 1A8D 8E5D FF77" /* P.t..4.]w */
+ $"909A 8111 3123 8200 AFC2 0525 13A1 C383" /* .1#..%.Ã */
+ $"5472 BA45 8D58 8818 E5CD 6FEA D858 47C5" /* TrEX.oXG */
+ $"F5A0 7773 3C86 1CE8 9A67 FAA7 5726 643B" /* ws<.gW&d; */
+ $"1BF8 C2C1 E7AD D3CE E89B 97C4 0210 1FAE" /* .蛗... */
+ $"9319 1F1F 7F89 0B76 5918 7EF8 9947 CF44" /* .....vY.~GD */
+ $"6B5C 4B5D 5067 58A7 F35E CF39 7D01 C555" /* k\K]PgX^9}.U */
+ $"FCD3 E0F6 C1A4 9C8A 0B34 2D33 A20D F664" /* .4-3.d */
+ $"C174 CCE8 CAA1 C45F 2974 101B 86E7 68FD" /* tʡ_)t..h */
+ $"D6FE 8FE9 AC95 5D3D DE38 1829 E3B9 37FE" /* 鬕]=8.)7 */
+ $"5008 F383 21C4 4D57 AE92 9F94 A9BB 3A76" /* P.!MW:v */
+ $"705F 8D2D 889C EB93 FD2B 05C5 89F2 500E" /* p_-+.ʼnP. */
+ $"4220 A486 F348 D466 4D62 E368 540A 1D25" /* B HfMbhT.% */
+ $"AF7C A9EF 43CC E423 16FC 6EE5 C407 DDAE" /* |C#.n.ݮ */
+ $"EB28 23D2 3FDE 40EF 2ED1 D53A B87B 9C40" /* (#?@.:{@ */
+ $"80A2 77EF CC8F D34E C33F 7BB4 4748 E4A6" /* w̏N?{GH */
+ $"B177 05FF 085F CBF9 F1A0 8CA7 4200 32AC" /* w.._B.2 */
+ $"7B87 A517 EA04 C96C F9A3 C6ED 5FFF 7FED" /* {..l_. */
+ $"D5EB F835 3E15 6882 AED1 72BF B42C 87DE" /* 5>.hr, */
+ $"8111 0F7A 5DA6 CFEA 79A7 9174 89E8 E637" /* ..z]yt7 */
+ $"7E56 B53E 9987 2013 8F1A 06A5 07A2 1856" /* ~V> .....V */
+ $"FE30 E259 128A 968B 881F E88D 8566 98D7" /* 0Y..荅f */
+ $"C815 C397 64E8 9FAC 059A 5922 C863 F220" /* .×d蟬.Y"c */
+ $"EC2A 4AF8 5DB2 A77D FC6A 3AC1 357E D5CC" /* *J]}j:5~ */
+ $"142B 85B0 FEE0 2579 D845 CDFD F507 4C58" /* .+%yE.LX */
+ $"D86A F299 0BBC A0A8 39A4 03D9 2144 6B3E" /* j.9.!Dk> */
+ $"6D67 242B 0FEB B6F4 79CB 58ED 2338 DB9F" /* mg$+.yX#8۟ */
+ $"6EC7 1626 0FDD 0C77 1E00 2AE4 8B46 F39C" /* n.&..w..*F */
+ $"6268 EE5F FF49 B5C8 30E7 9859 E163 A9D4" /* bh_I0Yc */
+ $"C50F BC0C 46BB BBC7 9FFF 7D54 E81B 8936" /* ..Fǟ}T.6 */
+ $"A032 72E8 6576 5FEA 9A3F A5A9 9E58 B9C2" /* 2rev_?X */
+ $"A089 A039 5695 766F 3295 A1FF 7F5F FBDB" /* 9Vvo2._ */
+ $"A3FD 1F1F 76AD 6787 B4D0 D1A5 E4EA 72F8" /* ..vgѥr */
+ $"0276 0C35 EC34 65A8 000A CA35 6AAE 1800" /* .v.54e.5j.. */
+ $"99CA 1B65 F56D 6C3B DF33 AB08 94A3 15CF" /* .eml;3.. */
+ $"C9B8 D32E 35D7 D32D 57E6 86D2 A5BE 2B3A" /* ɸ.5-Wҥ+: */
+ $"4535 2C10 41FE FDA9 E202 3927 BC4F 5F90" /* E5,.A.9'O_ */
+ $"E637 311C DB00 E394 09F3 005B 7DB3 0DFE" /* 71...[}. */
+ $"9F1F CAA4 DD03 B381 8330 AB38 E13B 2F9C" /* .ʤ.08;/ */
+ $"3DB5 01C5 100A 5D16 5C8D 792C FA0E 1A85" /* =..].\y,.. */
+ $"E3FF 467A 6333 E4F1 F27C 9E2B 161E 18F0" /* Fzc3|+... */
+ $"3F30 9C2C 631D BE74 4B7F 6E21 3FBC 404E" /* ?0,c.tK.n!?@N */
+ $"E063 4152 D02D 68E4 869C E7E9 E4E6 4E95" /* cAR-h䆜N */
+ $"3A66 1FDA 1145 DFB4 0796 9F97 503C E2C9" /* :f..Eߴ.P< */
+ $"7F49 D644 1290 98BD 8EA0 2FA1 CF00 E383" /* .ID./. */
+ $"C003 7BFE D79B FF62 A38E 2C39 A64D 19FF" /* .{כb,9M. */
+ $"6A9D 976A 994B 6626 4F4D 7DC8 F0CA EDB7" /* jjKf&OM} */
+ $"F99E 21E2 36B4 3AAB E787 C6A2 1783 F623" /* !6:Ƣ.# */
+ $"8755 1F66 AFBC 2959 6E6E 2E61 AC85 2198" /* U.f)Ynn.a! */
+ $"9978 EDFD 334C F0D6 7F76 9AF2 47AF 5E4B" /* x3L.vG^K */
+ $"805C 54D3 5F2A 16C5 6675 C288 B44F F851" /* \T_*.fuOQ */
+ $"1CE9 9F21 BF10 92B9 2366 5D69 AE8E 9989" /* .!.#f]i */
+ $"36A6 F9E6 75A7 0D47 C8B5 8727 0138 2BFA" /* 6u.Gȵ'.8+ */
+ $"5C4A 649C 123C 225E 2CEC 2007 5016 3F11" /* \Jd.<"^, .P.?. */
+ $"B8FF 60CB 7150 EBFD DFED 147E 0A98 E096" /* `qP.~ */
+ $"6B76 3AF0 39A5 5BF2 880C E857 174C 8735" /* kv:9[.W.L5 */
+ $"ACF6 E4B7 3477 6293 FF7C 8515 A8DE 4E73" /* 4wb|.Ns */
+ $"8E37 8A0C 4384 0CF3 FA01 15F5 9639 ED05" /* 7.C...9. */
+ $"C86B 28A0 C331 14BC 2817 FE9B F96A D60D" /* k(1.(.j. */
+ $"EBCB 14DA FF7F FF47 8A61 A2C7 F01F 10B7" /* ..Ga.. */
+ $"3DAF B01C B484 F55D 457E E3EC CC41 95DE" /* =.]E~A */
+ $"097E 8117 F77F FEFB A490 8B96 1300 2B37" /* ~....+7 */
+ $"19FA E04C 4188 E9F6 8950 0668 B082 8FD8" /* .LAP.h */
+ $"0B7D 6C32 4DBF F84F F4E9 ACAF 719A DC43" /* .}l2MO鬯qC */
+ $"9F8D A53E DF1A BDB0 AAA7 3267 9C2D 357A" /* >.2g-5z */
+ $"9CA7 1AE1 59C4 F467 83D3 308B 004A 42C2" /* .Yg0.JB */
+ $"909B F979 41CE AB25 C6D1 9FE9 46E1 E837" /* yAΫ%џF7 */
+ $"7ADF FF72 225E 4472 D323 F55C BE19 1E50" /* zr"^Dr#\..P */
+ $"BF94 6787 CADB F576 300A 054B 0A2D E9B2" /* gv0.K- */
+ $"5C15 DB84 E735 F76D 5477 D8A7 28AF FF7A" /* \.ۄ5mTwا(z */
+ $"7EB8 06F8 4FF4 E999 ED48 02C4 F111 F61D" /* ~.OH... */
+ $"8A6C 1CC6 AE54 94E4 8C5D 7655 4ECC 205F" /* l.ƮT]vUN _ */
+ $"DBF9 1BA7 83CC 52E9 E9C9 52A4 1151 7DE4" /* .RR.Q} */
+ $"051B E140 3FC0 D60B 0C11 663F 98A9 8B53" /* ..@?...f?S */
+ $"776E BD8D 7678 AD62 ACEE 033F D134 B61E" /* wnvxb.?4. */
+ $"F86D 3384 AAE7 9074 F7CF 4509 F9B3 4F30" /* m3tEO0 */
+ $"F32D 840F 1DDA E6B1 BDD4 951F 2379 F890" /* -..汽ԕ.#y */
+ $"530A E915 A228 D3D8 B188 C692 2767 D628" /* S.(رƒ'g( */
+ $"12F8 3FFA BA23 F76B 1D92 1FC1 EA73 30FC" /* .?#k..s0 */
+ $"CE72 0CA5 6695 6326 F93B B52F B9D4 F6F0" /* r.fc&;/ */
+ $"FF07 727B 80F9 EBFC 2176 788C 333F 4137" /* .r{!vx3?A7 */
+ $"E82C D066 6088 A8EA 168F 40EA F1EE BDE8" /* ,f`.@ */
+ $"C023 75BA 66F2 335A 98C0 31A8 2137 7A7B" /* #uf3Z1!7z{ */
+ $"DA0F 3962 D4B3 3E6E A5A1 3A19 5EED 48A7" /* .9bԳ>n:.^H */
+ $"7617 DE46 D178 BC48 D85D 7821 DFEC B30D" /* v.FxH]x!. */
+ $"F84F B7CD 80ED 55C7 9E1A F58D 7085 F625" /* ÒUǞ.p% */
+ $"F564 3BF5 A09E B353 4E45 2F0B FC02 ECFD" /* d;SNE/.. */
+ $"539F 529C A652 34F1 C0CC BC04 FCE0 2C7D" /* SRR4̼.,} */
+ $"A8A9 9E67 79E9 ADE4 ABEE BFF6 4793 1B91" /* gyG. */
+ $"413A 4302 7FB5 190C CB09 0DC9 3B33 E18D" /* A:C.....;3 */
+ $"DDAB 23C5 FABA 5DC1 554F C940 B964 7F6A" /* ݫ#]UO@d.j */
+ $"A781 2032 BF9E 0262 7C99 CD96 9251 9FB6" /* 2.b|͖Q */
+ $"1C69 9141 7A13 3022 DE48 DA68 C959 A0B5" /* .iAz.0"HhY */
+ $"0F3A F19A 9301 5051 5855 9D7B 5173 EE47" /* .:.PQXU{QsG */
+ $"2B0D DB30 28CC 5E41 46F1 E79F 90B3 AB2A" /* +.0(^AF矐* */
+ $"CAEA 6128 10E1 2CC0 DDC2 EBF1 26F1 E1DC" /* a(.,& */
+ $"EB87 87ED 55F3 827C A066 D932 FCF7 B134" /* 뇇U|f24 */
+ $"8BC9 CC70 B38C B019 8372 EA60 7C36 4D38" /* p.r`|6M8 */
+ $"F82F 16C2 2390 A91B 203A 8EBE D0EC E278" /* /.#. :x */
+ $"7AC4 8493 A217 6AC3 B85A 470D D74A B91D" /* zĄ.jøZG.J. */
+ $"0180 7BAB 4088 5671 6268 5B4A 54FE C179" /* .{@Vqbh[JTy */
+ $"1600 7677 830B 0E81 EEB2 6124 33F8 DBBD" /* ..vw..a$3۽ */
+ $"1B35 D1F9 0568 3891 0ECB A836 5A86 9636" /* .5.h8.˨6Z6 */
+ $"9F87 052F 0D66 8AA3 3362 1651 9B33 9057" /* ./.f3b.Q3W */
+ $"5B98 05EB 23CE 099F 2358 444E 0D64 A9F9" /* [.#Ɵ#XDN.d */
+ $"A569 7D56 A50D 6256 69AF BDD9 7847 D651" /* i}V.bVixGQ */
+ $"73E2 F76E F277 58DC BFB9 7B8E 593C 0E97" /* snwXܿ{Y<. */
+ $"3C4E 507D FA6C 7984 FAC8 6C42 6882 78AC" /* jXL..Τ */
+ $"884B 2DB6 09F4 F51B 546F CD71 F4D3 6452" /* K-.ToqdR */
+ $"B6E5 3EFF 66F8 0D60 D916 F755 1348 E250" /* >f.`.U.HP */
+ $"D8E2 6858 B424 B84B D59A 6885 24D3 D845" /* hX$K՚h$E */
+ $"692F 4313 B616 584F 7419 0661 0064 C281" /* i/C..XOt..a.d */
+ $"A873 131C 5296 042B 1FD5 3EED BFC2 659E" /* s..R.+.>e */
+ $"C4C7 2720 8002 28DD 141A BBE7 99F8 028F" /* ' .(... */
+ $"8262 0577 5240 EB9A F866 2901 B75B 3FED" /* b.wR@f).[? */
+ $"9E78 9071 83FD 02BC FCDE 2135 42D1 F184" /* xq.!5B */
+ $"309D F47E DF10 F797 4268 120A D377 04A4" /* 0~.Bh.w. */
+ $"2998 099E CA35 66AD FB20 8C14 1E0E 714E" /* )ƞ5f ...qN */
+ $"B9A7 70EE 5BA0 7A18 FC16 97EB 1A98 CB6C" /* p[z...l */
+ $"4B5D AC5B 3DC2 2062 E74A 18F2 0A76 6085" /* K][= bJ.v` */
+ $"EF50 1C2F FE23 117A ED22 7BE8 0231 BC71" /* P./#.z"{.1q */
+ $"707C 6736 BC3C 38AA 81C2 66B6 DCE5 13E9" /* p|g6<8f. */
+ $"3076 C7AD 8C4B A962 C37E 4605 CDC0 8247" /* 0vǭKb~F.G */
+ $"190C 41CD 6923 8D00 BC94 5B5F E2AB 443B" /* ..Ai#.[_D; */
+ $"3C5A 7FF6 213C 9E2D 9224 70B1 EA9B 7D36" /* .[.j,f */
+ $"BE8E C996 696D 58EC 6CFE 40AD 3C95 EAF1" /* ɖimXl@< */
+ $"4591 1D62 836E D2E3 4CD8 E4A6 F4F4 C456" /* E.bnLV */
+ $"A70C B3BE AA22 A188 DAB5 D3F7 4B7B 865D" /* ."ڵK{] */
+ $"4D89 77E6 99A6 46C9 14BA 25E1 55C5 ADBD" /* Mw晦F.%Uŭ */
+ $"4379 96AF 26DA A399 F009 AE75 248B 0285" /* Cy&ڣƮu$. */
+ $"B370 5704 6429 C8AF 0BD3 A332 90B2 8351" /* pW.d)ȯ.ӣ2Q */
+ $"F715 32C9 347B EADE 2EBB 9D97 BF95 0C5A" /* .24{..Z */
+ $"9203 1EC5 7A8E CF46 C28C 39A2 DE74 C656" /* ..zF9tV */
+ $"F0BF 3ABF CE5F BE71 B7CE 2FF3 F41B F3EA" /* :_q/. */
+ $"8BE7 B07E AB17 4F87 74FC FDE8 EF9F BF3F" /* ~.Ot? */
+ $"A9EB F3D5 3F3E 83BE 7BAF EA5F E0FE 7ADF" /* ?>{_z */
+ $"E7A7 7F9D 3FE7 3BF9 FA77 F9FA 3FF9 F7C7" /* .?;w? */
+ $"E7E9 871D E84F CFE8 83FC FE85 E3BB E7E7" /* .O */
+ $"AFFE 7D7F F9F5 57E7 AF70 6F9D 2DF3 9ED7" /* }.Wpo- */
+ $"C81E F903 FEAB 8EF9 F50D F254 3F3E A4D1" /* ...T?> */
+ $"DD53 F3F6 F37E 7EC9 4775 1BE4 77F9 EEBF" /* S~~Gu.w */
+ $"9ED1 F9DC 00F7 91DD 3D8B A94D D83C 797F" /* .=M.i.L.. */
+ $"F1D0 53DE 91D8 6723 A047 11C7 57B3 C46C" /* Sޑg#G.Wl */
+ $"84A7 C2E1 C7CC 185C 9F21 7E2B 0F2E 64B1" /* .\!~+..d */
+ $"D734 4142 C65E A2FB 9ABE B62D 076B 2B65" /* 4AB^-.k+e */
+ $"9D6E 63F1 4825 79A9 3936 C7A7 531F EF6A" /* ncH%y96ǧS.j */
+ $"F859 3084 0421 88A9 A648 8641 ACEB 95DE" /* Y0.!HA */
+ $"2E94 E3C9 442C 13AF CF63 0404 C5C6 5CE2" /* .D,.c..\ */
+ $"CE90 F9CC A1B6 5713 031E C239 174F CC40" /* ΐ̡W...9.O@ */
+ $"BCA4 EA9A EB6A 1D24 7962 F735 AF5A B937" /* j.$yb5Z7 */
+ $"AC3B A12E 10A8 D332 6FE3 8ED1 6F01 B198" /* ;..2oo. */
+ $"1543 A6A1 367E E226 5D76 B860 E0E1 8963" /* .C6~&]v`c */
+ $"AA7A F9EE DABF DB52 28F6 69F9 273F 8729" /* zڿR(i'?) */
+ $"1D00 C7D2 20B9 3483 2125 88FC 94C0 54A6" /* .. 4!%T */
+ $"42FE F4E5 DA50 F495 F88B 5986 21CB 22E8" /* BPY!" */
+ $"EE5E 0622 2CB0 BCC8 0C6B 2B2B 23EB 0506" /* ^.",.k++#.. */
+ $"A40F 09C2 726F 0C1F B8D4 55D3 2FE4 D629" /* .ro..U/) */
+ $"94C2 C913 BB28 4394 A9D2 8684 9E47 0445" /* .(C҆G.E */
+ $"B6CF 3E21 7072 B435 310C D7A3 063D 942B" /* >!pr51.ף.=+ */
+ $"6AE9 350C 9442 C76E 1E6B A8C1 18A3 4309" /* j5.Bn.k.C */
+ $"6D55 E62F FC4E C4E9 6CF8 241D FAC5 2CC9" /* mU/Nl$., */
+ $"E020 6B12 31A6 9B11 710B 8850 FABE 5D63" /* k.1.q.P]c */
+ $"068A 4018 2A90 93A5 9715 531E 8E7B AD7A" /* .@.*.S.{z */
+ $"2921 115F 19CE 2B6A F643 DE89 2E76 890B" /* )!._.+jCމ.v. */
+ $"5A69 1668 6A02 061F 4832 24EB FC82 6B4D" /* Zi.hj...H2$kM */
+ $"9B08 D22F CBBF D348 D18F 8B0C C684 6851" /* ./˿Hя.ƄhQ */
+ $"6CA9 E2FA 43CE 8A9A 7A58 FB28 0208 A6E0" /* lCΊzX(.. */
+ $"1ACF CE23 C624 5A38 C640 72C0 26F1 2818" /* .#$Z8@r&(. */
+ $"07FD 74D9 F3D6 ED38 CA01 6485 CE8C 41A3" /* .t8.dΌA */
+ $"02C6 18E5 E4D2 2043 FA62 8768 3E14 7E80" /* .. Cbh>.~ */
+ $"F2EB 67AD F743 75A0 2DDF 8724 1468 F88D" /* gCu-߇$.h */
+ $"9BF3 11EB 4D4C B372 D210 9B58 BAA0 646A" /* .MLr.Xdj */
+ $"222F 5E5A C781 1D86 5652 1B57 07AF C8FA" /* "/^Zǁ.VR.W. */
+ $"9E77 DD7B FCB9 1D4C 8573 7B03 95AC D2DA" /* w{.Ls{. */
+ $"3314 4FFC CAF4 39B5 EAF4 9617 2CC5 9258" /* 3.O9.,ŒX */
+ $"8F99 8C44 8D44 DA10 F1A5 51F7 5533 F13B" /* DD.QU3; */
+ $"CDDC B6D6 BE71 3C92 2628 3B69 DC32 C623" /* ܶ־q<&(;i2# */
+ $"F497 3959 034A 67C3 10A8 26E2 572F B4B1" /* 9Y.Jg.&W/ */
+ $"6C5C 3E47 FB9F AA7D D9A4 7A08 4BBA E854" /* l\>G}٤z.KT */
+ $"4370 CE77 C69A 11EB 1BD8 B9AF 8225 BC8E" /* Cpwƚ..ع% */
+ $"A261 1CAA A74D B531 A0B9 D462 35DF 9C21" /* a.M1b5ߜ! */
+ $"73F1 EAC1 A13C E0BC AF6C 8111 03DF AF1F" /* s<༯l..߯. */
+ $"4748 1866 322C 535A 7C5E D2BC 4304 3541" /* GH.f2,SZ|^ҼC.5A */
+ $"F14E 8412 296B 2C37 B642 9B4A 6417 EABB" /* N.)k,7BJd. */
+ $"9034 1260 1B81 AFCC F903 4FAA A3F2 339E" /* 4.`..O3 */
+ $"F332 B080 8C32 31D7 4DEA B260 F190 43F1" /* 221M`C */
+ $"CCD5 A584 5C1C 343F FB87 CEA5 F246 8EAC" /* ե\.4?ΥF */
+ $"C407 302C FEE7 3F59 F4B8 74ED ED62 D373" /* .0,?Ytbs */
+ $"A4BA B53A 0554 5403 9C2C E745 1087 5546" /* :.TT.,E.UF */
+ $"2671 7BA6 3A33 703E BEF5 CD28 10A5 462B" /* &q{:3p>(.F+ */
+ $"D902 9493 B2FC 176F 34BA 5A2D D2BC 0CBB" /* ..o4Z-Ҽ. */
+ $"5AB7 B854 08D3 CD79 3C71 1A89 E643 2FBC" /* ZT.y */
+ $"6757 09A3 C49E A9AD 7201 BFD9 B3FA 52B0" /* gWƣĞr.ٳR */
+ $"0000 0030 908B 4E52 45F1 3CBE F04A D2E4" /* ...0NRE..Sb'. */
+ $"5AE1 DA5A 8A02 140E 5275 5433 BCDF 8038" /* ZZ...RuT3߀8 */
+ $"0050 856F A747 857B 8376 255A 9185 28CC" /* .PoG{v%Z( */
+ $"301A EC7C B5D3 89CC A695 1C87 8266 7CA8" /* 0.|Ӊ̦.f| */
+ $"A203 7467 426E 9507 3DB3 1F90 F66B DC9F" /* .tgBn.=.kܟ */
+ $"ACF6 CDB2 4FCC 5AE4 2F94 F555 CB9E D8AC" /* ͲOZ/U˞ج */
+ $"950B FF38 0FE4 5056 E5E8 F371 9A98 5697" /* .8.PVqV */
+ $"6319 5197 B545 E594 AADD B2F2 CA27 260D" /* c.QE唪ݲ'&. */
+ $"9BAF 0E45 0846 5573 F96B 7AC1 CC89 E86E" /* .E.FUskz̉n */
+ $"61E6 72E2 05E3 9F8D 26D8 F178 BCC0 0B18" /* ar.㟍&x.. */
+ $"967C EA80 F7C1 45AE 8FAE A41C 41B6 438E" /* |E.AC */
+ $"46EF 1094 D558 01A8 BBAD 7380 DA27 2772" /* F.X.s''r */
+ $"9D67 1330 A48C 62EA 0643 14EC 3E22 A055" /* g.0b.C.>"U */
+ $"4E39 4E23 853D C587 99C8 CFD2 AEBB 9843" /* N9N#=ŇҮC */
+ $"ECC9 0241 3253 8CF1 E068 1936 3BB2 56D3" /* .A2Sh.6;V */
+ $"46F7 6A71 98D1 A300 1337 B3B0 7ABB 5752" /* Fjqѣ..7zWR */
+ $"DB7A 7F85 5CFE 59DE A852 6987 DC11 C6B5" /* z.\YިRi.Ƶ */
+ $"E45B 7DD1 1F3A D566 C16D 3FBF 7BAD 7201" /* [}.:fm?{r. */
+ $"B71C C74C A678 C2BB 10FD 534A 5E5D 79C0" /* .Lx».SJ^]y */
+ $"000C 2336 9113 B9C5 5D4A 1756 1C8B 0CD6" /* ..#6.]J.V.. */
+ $"AA8B DFF5 6CE3 CDAB B9B4 D85C 93FF 76AA" /* lͫ\v */
+ $"15CF CF39 A864 B236 A172 03A1 65A1 9F76" /* .9d6r.ev */
+ $"FF55 00DE C7D0 C767 2787 9EB3 7CE5 C5BC" /* U.g'|ż */
+ $"8E08 6085 37A2 229C 73D3 2CC5 F6E8 AA1B" /* .`7"s,. */
+ $"AF13 15FD EB95 2609 6006 23C6 7910 8FFE" /* ..&`.#y. */
+ $"8C5E 60A6 A558 E899 0A05 0F37 EC94 8E51" /* ^`X..7씎Q */
+ $"7838 9A11 8483 88B0 E284 B88D CDEA 9D56" /* x8.ℸV */
+ $"5C7E CFA2 8001 847E ABC9 360E 17EA 2D34" /* \~Ϣ.~6..-4 */
+ $"8314 96C0 3810 D88F A54E 6E7C 6030 76FA" /* .8.؏Nn|`0v */
+ $"18A4 A549 37FC 11F6 E77F A883 C794 71AC" /* .I7..ǔq */
+ $"55F1 7F88 664A FEE4 01C4 5576 1BE6 9C41" /* U.fJ.Uv.A */
+ $"D800 6121 2FC0 E681 A16E 21AF D5FE 0E80" /* .a!/恡n!. */
+ $"A800 0000 3090 8C40 9184 82DA 7187 51C6" /* ...0@qQ */
+ $"44DC C420 FF13 98AD D100 2C07 132C 8D6C" /* D ..,..,l */
+ $"5D16 A089 D8F6 E77F A870 24F1 5461 DB69" /* ]..p$Tai */
+ $"0F41 5029 7A41 B55F 0000 0184 846A 850C" /* .AP)zA_...j. */
+ $"241E E628 EDA6 4000 3090 6AA2 F6E7 7FA6" /* $.(@.0j. */
+ $"26C9 5EDE 9BB6 A1B1 51E6 8D5D F800 0061" /* &^ޛQ]..a */
+ $"19B3 E47D 6F6F DB03 0885 4A53 27BA 38F5" /* .}oo..JS'8 */
+ $"35A0 1AB9 0A95 8FAE 85E3 F528 5A8B E645" /* 5.(ZE */
+ $"8419 0456 6A38 D0DA 4948 8987 2323 1216" /* ..Vj8IH##.. */
+ $"A4F8 FD1A C374 D30C BF43 CD86 B984 F23C" /* .t.C͆< */
+ $"5487 B47F E217 F15A D8CF A66A 96A9 EAF3" /* T..ZϦj */
+ $"F966 0205 D320 1556 5C3C 073A 8E66 1E3F" /* f.. .V\<.:f.? */
+ $"A74E FF2C BC45 E9DB 3B25 FF7F 46AD 13A9" /* N,E;%.F. */
+ $"5C21 458B 6AD4 829C 1DFB 54BA E5F2 3C76" /* \!EjԂ.TF...! */
+ $"2CFF 4F5F FD2E 999E B77D A47D 61B4 2C01" /* ,O_.}}a,. */
+ $"D397 493D CB48 AB27 EB4A 13A7 8C89 7F64" /* ӗI=H'J..d */
+ $"6C08 FF21 9696 98C7 6248 1615 541F EC81" /* l.!bH..T. */
+ $"8E18 FAED 5257 3D01 2CBB 74C1 DA64 81A0" /* .RW=.,td */
+ $"39BF B272 4068 15E8 A44A 007C 732C D9DC" /* 9r@h.J.|s, */
+ $"1CF8 4612 8611 A36D 67BC 6E6A EA14 9D87" /* .F..mgnj. */
+ $"2962 C4B4 5442 9564 BFAA D0FB 991F 4288" /* )bĴTBd.B */
+ $"7CB2 2A41 333E 7827 5070 F3A6 67FF 7FFF" /* |*A3>x'Ppg. */
+ $"7601 CFFE E44B 4413 809B 3877 B59D BC19" /* v.KD.8w. */
+ $"548F 0A5B C1E9 A8D7 6B5D 8EC8 8A1A BB3D" /* T[k]Ȋ.= */
+ $"3CD1 3139 57CF 9E94 00E9 CF6E 3830 3561" /* <19WϞ.n805a */
+ $"8DE4 A59F FD1B 45CF C608 D0FD 5D48 485E" /* 䥟.E.]HH^ */
+ $"D5A3 30A5 F9EE 5D55 B9C2 E552 3A9F B087" /* գ0]UR: */
+ $"FE87 5DBC 5E73 0F16 9221 2B0D F399 AB6F" /* ]^s..!+.o */
+ $"9950 0000 0000 0000 0008 2444 88CE 3BBD" /* P........$D; */
+ $"CC78 4932 9C47 DEFA 74E7 91BF 9827 FF7B" /* xI2Gt瑿'{ */
+ $"77EB 9B1B 9F7F FDAB 6552 35A1 0C22 E9C6" /* w..eR5." */
+ $"42E3 FF7F FF7F E9FD A23B 2F7D 3ADD FC90" /* B..;/}: */
+ $"E4A1 F8B9 0560 0F86 D7FE 3F46 C953 2A03" /* .`.?FS*. */
+ $"F1C7 F5AE D0C4 B743 EA67 B694 E47D 45CD" /* ķCg}E */
+ $"6F92 178A E383 8B71 800E D9AF A55A F149" /* o.ニq.ٯZI */
+ $"A838 5671 4C9A 8915 447A CDA9 C6B7 DD68" /* 8VqL.DzͩƷh */
+ $"A5FB F404 CF63 A62E CC5E 89D3 FDB2 C841" /* .c.^A */
+ $"7A37 5B5D 5958 4B46 20B1 D389 5188 F9FE" /* z7[]YXKF ӉQ */
+ $"8FF1 D718 6C8C 3254 0000 0000 0000 0000" /* .l2T........ */
+ $"0000 00A1 EBEC C33A 8622 D0FF 7FFF 7FFF" /* ...:".. */
+ $"705D C7FE 49E7 01B7 A65D D5BC 2041 80B0" /* p]I.]ռ A */
+ $"89ED F68A A000 F438 EBD0 C908 0B67 414F" /* .8..gAO */
+ $"C8F1 4312 2EC8 05E6 CA19 184F FF5B BA24" /* C.....O[$ */
+ $"57E8 A6B4 FBD3 FF79 4D8E FF44 BC50 6938" /* W覴yMDPi8 */
+ $"137F FF7D 6251 8D1A 9B33 CF7E 0BCB EAD7" /* ..}bQ.3~. */
+ $"2E0D EAE0 0003 0906 AA2B 66A8 C950 0000" /* .....+fP.. */
+ $"0000 0000 0000 0000 0001 1E1F F600 1848" /* ..............H */
+ $"4BF6 7A7C F92D 3E7A 437E 2254 51EB 4AF5" /* Kz|->zC~"TQJ */
+ $"C6BF 442A 12CD 51FF 7FFE B8AE D0C4 B1D0" /* ƿD*.Q.ı */
+ $"7CF7 019D A0ED 2CC4 01BC 1A94 212D 43EF" /* |.,..!-C */
+ $"1274 9B67 94D5 062D 5FCC E23C 4816 416B" /* .tg.-_ށ01*uO5. */
+ $"7FFD A983 95A9 71E7 A7A3 7D3F F944 0A7E" /* .q秣}?D~ */
+ $"24D9 966F 6B6A 0FD0 23AC 7000 0000 0000" /* $ٖokj.#p..... */
+ $"0009 D4AF 9196 E5A4 B8AC 407F F9EE C709" /* .ԯ夸@. */
+ $"F927 08C3 CA6C 3A3A D90F E306 BCC4 6D6C" /* '.l::..ml */
+ $"C7A9 F042 6F4C FDC9 60AB A087 6338 8F12" /* ǩBoL`c8. */
+ $"4D29 D00C C575 C7B5 9FA4 E96C 1FA0 AAFB" /* M).uǵl. */
+ $"09E9 8DF9 1BE0 C62D 3C1D F55B DD77 A0FD" /* .-<.[w */
+ $"5C16 D959 BA08 0000 0000 0000 0145 AE01" /* \.Y........E. */
+ $"712A 0E06 1212 70CD 94A8 0A2C 18F5 1E3B" /* q*....p͔,..; */
+ $"52FA C8F6 56F3 F7E4 69E6 54A0 3FF6 2487" /* RViT?$ */
+ $"C65F 0EFF 7F4F 8D07 C817 FE78 727D 3C75" /* _..O..xr}~bR.JP */
+ $"DADA 8602 5949 900F D2B6 D10D 2661 3014" /* چ.YI.Ҷ.&a0. */
+ $"4541 17FF 2106 00DF AC82 0046 09BB A5AF" /* EA.!..߬.Fƻ */
+ $"CE51 0EBF F9AD 787A 907F 9F9A 628F E16B" /* Q.xz.bk */
+ $"C015 137E 9C68 FCFA A817 FF7F DD6B FDB6" /* ..~h..k */
+ $"3DA3 7C9D 3309 0B8A B551 C370 4401 CD7F" /* =|3.QpD.. */
+ $"7503 8425 E297 1B3F FF49 4FF8 3583 685A" /* u.%.?IO5hZ */
+ $"B273 A7F7 CEBF F27E D78C 3E35 5332 152B" /* sο~>5S2.+ */
+ $"033C C7FF 7B97 1AF0 F2A4 3C88 CC7B B96E" /* .<{.<{n */
+ $"65B3 533C F2F8 5E4B 6DBF 7D78 A316 310F" /* eS<^Km}x.1. */
+ $"6B09 FD62 88D7 77FF 7447 524E C235 00A3" /* kbwtGRN5. */
+ $"8F5A 4BEE D240 6154 C17C 2CC2 68F0 A959" /* ZK@aT|,hY */
+ $"2F12 9B74 7E8E 423F BB1D BC9A C75B C618" /* /.t~B?.[. */
+ $"19D1 C6CB 0A15 BD77 99BD 96D3 CC07 F5AC" /* ..w. */
+ $"D2ED DE5E F19D 5CFC B2B0 5407 0386 32C6" /* ^\T..2 */
+ $"5F49 7BAD 3270 BF81 CF97 C934 7E23 7237" /* _I{2pϗ4~#r7 */
+ $"727B 0FA2 6DB0 D717 8C73 B109 E089 AAC5" /* r{.m.s */
+ $"7D98 9061 2956 EA75 0135 E870 3430 4E9B" /* }a)Vu.5p40N */
+ $"C76C D3D6 7EEB 83DB 9EEB C9A0 983D 1327" /* l~۞ɠ=.' */
+ $"62D8 FD62 CA51 7135 80B0 ABFF 431B 9843" /* bbQq5C.C */
+ $"68B4 A929 44E4 1F3E E3A1 22AA 5E87 579C" /* h)D.>"^W */
+ $"745B 9CA4 438C BFC2 9A1B 17BE BA75 06E4" /* t[C..u. */
+ $"0108 698C E4F2 341C 24CC 7913 6237 ED48" /* ..i4.$y.b7H */
+ $"E797 4D7A 0D5A D1EA ACDF F974 4F9F 9139" /* Mz.ZtO9 */
+ $"6A87 3972 A1C8 0A2B 1E8B 6DB7 B811 4AB0" /* j9r+.m.J */
+ $"EEE8 BB23 959C 239D 4165 DBED ACE4 F133" /* ##Ae3 */
+ $"8276 40BB C447 29FE 3EB6 5BA4 A6D0 2441" /* v@G)>[$A */
+ $"1431 8B2D 8BFD 96B7 C8C2 9F9B DFB9 2B06" /* .1-߹+. */
+ $"E191 00FF 7E4B 8281 D609 D163 B8CB AF9A" /* .~Kc˯ */
+ $"0995 7385 27FC 3C6A 5310 024D BAD1 8013" /* ƕs'x.(.e> */
+ $"4161 0037 E0AA D0CC 3AED 4E56 04E8 4456" /* Aa.7:NV.DV */
+ $"6E50 4626 C7E0 0790 014E 329B 0DDB D87D" /* nPF&..N2.} */
+ $"4532 6482 3F51 D651 3F62 D6EB 3BCA 75EB" /* E2d?QQ?b;u */
+ $"5444 A42F 3CFB 4146 19FB 5376 876F 6BC1" /* TD/U@.. */
+ $"3E83 51D4 9D13 DE0D F86D 3384 AAE7 9074" /* >Qԝ..m3t */
+ $"F7CF 4509 F99A 70B2 2E74 43C3 1363 021F" /* Ep.tC.c.. */
+ $"6482 E471 22FA 6265 2DF9 C302 43F8 9053" /* dq"be-.CS */
+ $"0AE9 15A2 28D4 FD7C 05AF C7B8 26D5 7C0F" /* .(|.Ǹ&|. */
+ $"22E0 4735 518D BE4B 4E83 1EF8 3FFA BA23" /* "G5QKN.?# */
+ $"3AE8 F76B 1D92 2038 64C1 EA73 30FC CE72" /* :k. 8ds0r */
+ $"0CA5 6697 157E 55C4 8BAE E703 6FF5 F743" /* .f.~Uċ.oC */
+ $"B456 C85F 3BFF 2629 95B7 02F4 F580 A49B" /* V_;&). */
+ $"1587 2316 D131 66C1 C6DB F3DC C409 D801" /* .#.1f. */
+ $"E2DA 30B6 73FD 99EF AA26 25D7 DF07 FBEB" /* 0s&%. */
+ $"094E 2E8E BD11 3C49 81BA E6BE 7666 ECBE" /* N..sW.6<%1 */
+ $"2C25 ACE4 F084 E842 60E8 355D 22BE 6FEE" /* ,%B`5]"o */
+ $"93F9 DDE6 DAED 4E44 545A FE4E 5EA4 0487" /* NDTZN^. */
+ $"36F1 52AF 8683 2DE8 F30B 72DE E807 2068" /* 6R-.r. h */
+ $"742B 7262 BF7E 516C D41F CA56 9DE1 E3B8" /* t+rb~Ql.V */
+ $"8D72 2993 C073 516D A343 DDE0 984D F06A" /* r)sQmCMj */
+ $"CD83 D22D C161 30D7 2E9A FED4 4E85 5D2E" /* ̓-a0.N]. */
+ $"CF72 2181 7F23 BC37 FF83 C30D 43E0 DBC7" /* r!.#7.C */
+ $"A277 B4D9 4E68 1FB1 AF7B 6D8B 8F43 2A1B" /* wNh.{mC*. */
+ $"9FE4 6CCC E826 C4AA 96C9 98A9 4D3B 981F" /* l&ĪɘM;. */
+ $"8786 8633 89B0 2B42 4266 BC27 0098 D2B3" /* 3+BBf'.ҳ */
+ $"E8E4 0D2C 6E14 D34D 3613 4568 4A1E 8E5E" /* .,n.M6.EhJ.^ */
+ $"E328 D89C 85D3 D8C2 C15B 3533 989F F015" /* ([53. */
+ $"1B49 871D 031B 2DBE D49E 880D A557 0694" /* .I...-Ԟ.W. */
+ $"60C3 47A1 CD2A 961B F73C 94A3 6B17 4268" /* `G*. */
+ $"8743 EB0E ED13 7955 DB28 2874 8065 2078" /* C..yU((te x */
+ $"97A7 6C13 2283 729A CCF9 E449 A627 7652" /* l."rI'vR */
+ $"E821 2F1F 574E 6887 1830 6E01 5A8D 02FB" /* !/.WNh.0n.Z. */
+ $"DB61 8EEF 4E08 B607 26FA 2B4C FF47 38FE" /* aN..&+LG8 */
+ $"9B7C A3E4 DB46 F898 CFE9 8F3C 8717 2975" /* |F<.)u */
+ $"9BA2 582E 86D1 D882 C127 EFD7 E71A B5DD" /* X.'. */
+ $"7772 6391 2722 2884 AA0F 9C72 9A96 F4F8" /* wrc'"(.r */
+ $"BFF8 869A FD00 3B41 A8C7 DCD0 2834 270C" /* .;A(4'. */
+ $"B93C 8702 5B16 8A32 0289 979B 0A35 3A7B" /* <.[.2.5:{ */
+ $"5870 6189 03C2 DD00 FAB2 B137 AB93 3EF5" /* Xpa..7> */
+ $"24E3 1FE1 A206 963E 1344 FE12 285A 5C52" /* $..>.D.(Z\R */
+ $"692E D315 6C73 2CBE 7ADC 81BC EBC8 13EA" /* i..ls,z܁. */
+ $"796B 6056 E9E7 D971 6E09 7086 252C 6A19" /* yk`Vqnp%,j. */
+ $"7351 153D F4C4 FE01 6235 917F B203 171A" /* sQ.=.b5.... */
+ $"1822 B42B 9226 D163 5572 7E7E C508 BEBA" /* ."+&cUr~~. */
+ $"D0D2 1C24 8D62 565D C183 F78D 4726 0541" /* .$bV]G&.A */
+ $"F829 10C7 1B21 E59C C4E5 7688 6268 FABC" /* )..!vbh */
+ $"C0B9 6C02 8820 3F44 54A7 8C83 F14F 61B8" /* l. ?DTOa */
+ $"707C AB32 9D59 F01E D18A 37FC 40F9 5C59" /* p|2Y.ъ7@\Y */
+ $"EEA8 E442 FDC8 CA73 970F 6EE0 1348 AC8E" /* Bs.n.H */
+ $"9D65 4DA9 2820 AEDB A7F3 C1B4 8E3F 8780" /* eM( ۧ? */
+ $"8DA9 F7D9 F116 DD30 778E 4FB6 49D3 AFE8" /* .0wOIӯ */
+ $"74B1 EB63 92B8 4210 3B22 3613 6784 41A2" /* tcB.;"6.gA */
+ $"5953 1EFF 16A9 1769 9852 73D0 B6C2 3523" /* YS...iRsж5# */
+ $"854B 1247 531D 47C5 7AFC 1B6B F0C4 2E2F" /* K.GS.Gz.k./ */
+ $"6C60 82C9 9C4D 2FAD 25A5 BEC6 8590 7388" /* l`ɜM/%ƅs */
+ $"5D15 9A65 E013 D48B 7D9C 34B2 A617 75A8" /* ].e.ԋ}4.u */
+ $"8E8E 2D91 1AEC 26D5 8B9C D03C A02A A829" /* -.&Ջ<*) */
+ $"738D 9A4E 8F31 82A9 39B7 7CFE D412 810E" /* sN19|.. */
+ $"1372 6D79 8D49 BAC1 9FFB 5D52 56AD 488F" /* .rmyI]RVH */
+ $"4E1D A232 7348 1FE2 483F AB9F A23C 0997" /* N.2sH.H?<Ɨ */
+ $"678D 1AB7 7F66 8927 7DED 2A66 E03B 05C9" /* g..f'}*f;. */
+ $"0420 66A2 E0AE E1B9 259E 64C9 FF0E A7C7" /* . f%d. */
+ $"5E9D 69F4 DD50 394C 344E 9DA9 6BCA 3D9F" /* ^iP9L4Nk= */
+ $"8FA9 C156 3248 2310 8AB9 6131 B94F FF36" /* V2H#.a1O6 */
+ $"DF00 9E80 5713 E9AD 3B07 879E C876 E063" /* .W.;.vc */
+ $"38D8 2776 3081 5810 1760 927C D93C EE88" /* 8'v0X..`|< */
+ $"518B 755A EBF1 B276 C65B 98DD 1421 8AA1" /* QuZv[.! */
+ $"27D2 A8B2 5E3E B7B7 8F73 CDCB 9896 D80F" /* 'Ҩ^>s˘. */
+ $"1D50 2E28 4F54 26A9 E68F 822C 3031 0A5B" /* .P.(OT&揂,01[ */
+ $"213A 8ED8 B1B8 C32C F5EF 6784 3641 A888" /* !:ر,g6A */
+ $"1AF9 941B E9B2 42C0 D9BE AAD7 2727 9E13" /* ..Bپ''. */
+ $"284F A530 1F62 679B 7860 E677 9E24 3663" /* (O0.bgx`w$6c */
+ $"E7D1 E4B5 D37B C274 9ECB CFA2 CC95 8A75" /* {tϢ̕u */
+ $"0C3A FB7D F074 1433 5EC7 1FC4 330E F221" /* .:}t.3^.3.! */
+ $"AE39 4A04 097F 4B21 27CD C193 39CB 3AB2" /* 9J..K!'9: */
+ $"0B6E D8E0 54EA 0F75 15F9 D75D 0D82 624D" /* .nT.u.].bM */
+ $"7D19 8E11 1AD0 764F CEA8 F0FE 2913 6841" /* }...vOΨ).hA */
+ $"89B8 CA62 C997 34B9 5950 4842 4F13 1ACD" /* bɗ4YPHBO.. */
+ $"39AD 30CA 4ED2 A17D 0B09 206B 9426 EF50" /* 90Nҡ}. k&P */
+ $"63C6 386E 74BB BDE0 420F 6154 5260 E1A2" /* c8ntB.aTR` */
+ $"8546 3192 76D1 1F38 268E 7AC6 049F 321D" /* F1v.8&z.2. */
+ $"CB51 35C7 067C 961B DA2F 4BCB E065 A8DB" /* Q5.|./Ke */
+ $"16C9 1F05 ABAF 93FF 333A F049 3161 6838" /* ...3:I1ah8 */
+ $"6E1A FEF2 564C 3F48 3800 BC9C F009 AF34" /* n.VL?H8.Ư4 */
+ $"2241 15D4 764F 4764 CEDF 6D7D 2E9C 8141" /* "A.vOGdm}.A */
+ $"1E00 E109 D827 DDAA A47C 2854 9229 8998" /* ..'ݪ|(T) */
+ $"9175 1FFB 496B 36C7 3695 3631 390B 06E6" /* u.Ik66619.. */
+ $"EB0D 3BA7 F1BE A2FF 6D3A 9FB6 FCFD ADEF" /* .;m: */
+ $"EDBA 3FDB 632F EDB1 BF6D BE7E DB9F 97ED" /* ?c/m~۟ */
+ $"45FD AB93 F6E8 7FED A7F7 DAC3 FC34 6F43" /* E.4oC */
+ $"E1DB 5507 762F DB7E 5F51 DDF5 1DFE DD07" /* U.v/~_Q.. */
+ $"E1FD A67F B4E6 7EDB 8BF6 AE3F B6BC D3E4" /* .~ۋ? */
+ $"D00B 0F87 A0FF 2AC3 3F6D 84F8 7475 99F2" /* ..*?mtu */
+ $"F382 BE95 100F 8FEE C1C8 D086 E345 E973" /* ..ІEs */
+ $"0FE4 06B6 7813 7115 B8D0 D91B C698 D10D" /* ..x.q..Ƙ. */
+ $"A370 844F 9330 E047 0201 F880 D146 6522" /* pO0G..Fe" */
+ $"8E52 12DE 3D1A 9231 5315 682E 4706 8685" /* R.=.1S.h.G. */
+ $"DA39 C538 9828 7ED0 714F 3E27 532A 3840" /* 98(~qO>'S*8@ */
+ $"C912 3E46 8205 3243 7835 7284 12D6 CCF5" /* .>F.2Cx5r. */
+ $"36AF 0975 8A27 4F44 3857 B5A3 160D 1391" /* 6u'OD8W... */
+ $"8005 2453 86CB 51B3 E493 EA9C 707F 7047" /* .$SQp.pG */
+ $"1DA8 1386 91D1 6105 7D84 1563 FC52 D5D3" /* ..a.}.cR */
+ $"EDA7 D4A0 4AC1 BB4E 49D6 62F4 CA92 CE8E" /* ԠJNIbʒΎ */
+ $"8DE0 84DC 418D ACE2 71AD 9206 3C98 3281" /* Aq.<2 */
+ $"104E A1F2 8982 6ED6 223E B67B BE6A C51F" /* .Nn">{j. */
+ $"4BF8 6C35 B6A4 5EC8 87BB 4834 7CF3 16AC" /* Kl5^ȇH4|. */
+ $"2453 B3DA 3503 94D6 2E70 2B6F 975E 21DB" /* $S5..p+o^! */
+ $"F409 0368 9A2E DFB2 7A4B 9A6F 4165 1173" /* .h.߲zKoAe.s */
+ $"A53E 329A 7925 FB80 9664 2C09 A9E5 715C" /* >2y%d,Ʃq\ */
+ $"E42F 8E14 EC68 05A8 D565 53C2 9C98 86A2" /* /.h.eS */
+ $"2C2D 6443 A1D0 A619 2320 6CD3 FE48 8006" /* ,-dCЦ.# lH. */
+ $"FC0F 3690 DF76 1FD8 2E16 0705 D91F EEBE" /* .6v...... */
+ $"E961 32B9 E252 4F0E EE4A 6F73 AEBB 014A" /* a2RO.Jos.J */
+ $"8F45 1551 9123 7C3A 13F8 1B57 EB6A 0AFB" /* E.Q#|:..Wj */
+ $"9AE9 65F5 9FDE 5F1C 7C23 84F1 6DCD CC52" /* e_.|#mR */
+ $"97DD 03E6 C571 0C23 F06B 9F21 2E2C 79D5" /* .q.#k!.,y */
+ $"1827 1436 E6FB C5FB BA11 617B 9B32 3DCD" /* .'.6.a{2= */
+ $"B0AD AA8F 53D0 2F20 849C D667 9436 A677" /* S/ g6w */
+ $"38C9 1B5A 26E2 B4D4 29BB 2D9E 56D4 652D" /* 8.Z&)-Ve- */
+ $"9A12 FC7E FBD5 0075 3EC9 64FE D0CE 06A2" /* .~.u>d. */
+ $"B63F 8731 DB8A 6917 26EA CCFC C1E6 10AA" /* ?1ۊi.&. */
+ $"704E 20EA 7518 0416 6BBE 2867 9470 9FE6" /* pN u...k(gp */
+ $"3D2A D178 6CD1 0B7D E518 4682 FC48 EA03" /* =*xl.}.FH. */
+ $"EFA0 ADEE B643 CDFD 796D C309 E13B 9DD3" /* Cym; */
+ $"82CC 1237 8D6B 9CB9 0438 AF1A 6190 136E" /* .7k.8.a.n */
+ $"A170 6109 4BA8 FD0B 4C10 DE61 71A9 A5FF" /* paK.L.aq */
+ $"5AD1 3895 15D0 912A BC62 5344 A7FD 7B9B" /* Z8.Б*bSD{ */
+ $"0360 8C71 DD04 20C7 4685 E6B7 7941 7BD7" /* .`q. FyA{ */
+ $"DBBB 3F61 742B D692 AD3D B778 6442 3DD1" /* ۻ?at+֒=xdB= */
+ $"A97E 1312 9AF4 05C1 7B33 9FE2 B3F5 D16C" /* ~...{3l */
+ $"875D 7109 13EC BB96 0664 DF60 1958 8DE5" /* ]q.컖.d`.X */
+ $"5B3F 3E75 9D21 0415 CE9C 3172 87C7 FF05" /* [?>u!..Μ1r. */
+ $"F787 4F14 E63B 4DCE 247D B3A3 475C 5336" /* O.;M$}G\S6 */
+ $"C794 AF23 6415 68C6 B893 1B16 8C81 3F43" /* ǔ#d.hƸ..?C */
+ $"C455 6EB8 8D5A 6919 4C3A D980 BFB1 3699" /* UnZi.L:ـ6 */
+ $"C790 C7D1 7B05 12AA 803A 172B DEF2 00CC" /* ǐ{..:.+. */
+ $"FF38 62C8 1815 1D3A F60D 2B9B 87A0 05C5" /* 8b...:.+. */
+ $"3587 0181 8754 06D7 2CFC 78B8 0C65 D5BC" /* 5.T.,x.eռ */
+ $"2903 C07C 0631 A0B9 E94E 192F DE4B 4924" /* ).|.1N./KI$ */
+ $"A4F0 BB9A 49C5 B751 38EB 7289 9806 FC5C" /* IŷQ8r.\ */
+ $"D822 ED93 CA92 14B6 5056 017F 7826 CDE1" /* "ʒ.PV..x& */
+ $"8401 9F6C A7F7 4AD5 E86F FDC3 05AF 6AE3" /* .lJo.j */
+ $"5EED 07E3 E5ED 7EE7 FB97 0B99 E191 605B" /* ^.~.`[ */
+ $"955A 6A3C 33C6 2BB8 9562 0E38 09C1 1ED9" /* Zj<3+b.8. */
+ $"8914 73AF E9AA 7B2F 214E 7EEA 98E8 B976" /* .s{/!N~v */
+ $"E058 E0B5 6DB4 5B1C F212 2C8E 0AE5 7100" /* Xm[..,q. */
+ $"BC1E 84CD 0598 7CF9 EA4E 1E96 2B4A DE9E" /* ..|N.+Jޞ */
+ $"6CE9 E79B 0336 9059 E109 DF83 A2AE 9E74" /* l.6Y߃t */
+ $"5BB1 0236 590C 66CD 0658 ECE5 50A2 EDDD" /* [.6Y.f.XP */
+ $"D7BB DAF7 548B 7F21 2A40 7821 FDA2 62B6" /* T.!*@x!b */
+ $"C862 B0B2 96CF 27D5 EC9D F5C8 42D8 F42A" /* b'B* */
+ $"5962 F21A D191 6744 1AE2 64B4 6744 A1B2" /* Yb.ёgD.dgD */
+ $"7625 FF0D F0CF A1F8 3273 C4B8 FCCE C2BC" /* v%.ϡ2sĸ¼ */
+ $"EDD5 F0C6 765F 2446 5EF5 2195 04EE C94B" /* v_$F^!.K */
+ $"C9C2 4F82 0D0F 6034 3560 64A8 C372 4E17" /* O..`45`drN. */
+ $"D4F6 B2DC 2162 A6DC 9B35 5F36 628E EA58" /* !bܛ5_6bX */
+ $"B2EF D0B8 79FF 3F60 39DC 34C9 8DD1 C7DA" /* иy?`94ɍ */
+ $"BFFD 9220 B977 42CD 1F4E 00C7 1A7B F29F" /* wB.N..{ */
+ $"9738 8D5E 3E5F BA1F D4BD 0592 2006 1216" /* 8^>_.Խ. ... */
+ $"D478 DE72 171A A49D 9C4A 0691 CD59 6FB3" /* xr..J.Yo */
+ $"F3C5 B3CD DCC8 5794 133C 85B3 176A 0C6A" /* ųW.<.j.j */
+ $"323E C74F 7CE3 4042 38AB E9CA 5700 E39B" /* 2>O|@B8W. */
+ $"26D3 62B7 59F8 966D A789 35AA C72D 9943" /* &bYm5-C */
+ $"C884 2E53 30B3 D351 EFCC 6BF8 9889 8AB6" /* Ȅ.S0Qk */
+ $"E284 F5A4 64BD F89F 98A4 8082 6292 48EB" /* dbH */
+ $"2039 4A80 D5D7 BEFC 2DC1 69A9 F484 9B4E" /* 9J-iN */
+ $"94C9 6E87 31A6 6D4E 23DB 77A9 37D5 CEE9" /* n1mN#w7 */
+ $"10B5 22DB 58B2 3DFB 973A E321 F02A 2985" /* ."X=:!*) */
+ $"AE7E F964 9A23 4B8D 90FD 75F4 9944 3CEB" /* ~d#KuD< */
+ $"C5C5 B0BA DA1C AA0A B59A EFCA 903A 972B" /* Ű.µʐ:+ */
+ $"10E9 2A66 2BAB 3E63 8312 E494 A82C AEE8" /* .*f+>c.䔨, */
+ $"DE3B 902F 4DE5 A934 7FA5 59D8 B355 75C7" /* ;/M4.YسUu */
+ $"DC51 6208 6D60 DE89 F04A F698 FC35 5341" /* Qb.m`މJ5SA */
+ $"A8C7 894B 5E8F 0638 0F21 36AB 23FF 37BF" /* ljK^.8.!6#7 */
+ $"CA6E C0CE 1C93 9BB1 FEA7 49E8 9BA5 7F32" /* n.I蛥.2 */
+ $"4C9D C8BA 821C 1E07 F277 BE63 241C D5F6" /* LȺ...wc$. */
+ $"07CB 7A98 E51E B0FC B312 223C 21BF 5DEE" /* .z..".5" */
+ $"1088 A628 490C 5F8D 64C4 7E17 CBD3 290D" /* .(I._d~.). */
+ $"91B1 B073 AD4A BF5B A1E9 6306 3E9A F87E" /* sJ[c.>~ */
+ $"2DEC 7390 2CF5 3305 93C2 15DA 7FFF 7FC6" /* -s,3.... */
+ $"0578 C93E BF93 5B69 25D4 E0F9 5726 0D7E" /* .x>[i%W&.~ */
+ $"9C06 C76B 1BF5 3206 F26C 4F57 6A13 7488" /* .k.2.lOWj.t */
+ $"07F4 1787 316E 19B5 2CB4 D7B8 23EE BF15" /* ..1n.,#. */
+ $"84F7 C141 CE7E 358F F83B 081D 2680 C5CB" /* A~5;..& */
+ $"2002 0FFB D329 10E3 1880 E0F6 29FF 14F6" /* ..)..). */
+ $"1447 24FF 4BDD 371B 00F3 0334 6FE5 D217" /* .G$K7...4o. */
+ $"8E7D 3CCB EC04 D019 0583 0792 D4F4 59E5" /* }<....Y */
+ $"0914 52FB CE7A 2EFD B899 ED16 38FB E1D4" /* .Rz..8 */
+ $"3312 BAAE 3EF3 45B7 2A77 62F3 4787 8900" /* 3.>E*wbG. */
+ $"050D 5A64 92D3 64E7 7950 4234 FCC0 0EC6" /* ..ZddyPB4. */
+ $"004C 8652 616B B5FF 87F6 72D0 400B 9574" /* .LRakr@.t */
+ $"7ADD 2EE6 F979 EE70 393F 5E65 09EC 561B" /* z.yp9?^eV. */
+ $"2FF4 F6FA 9226 2AA7 C2D2 4517 2258 15C7" /* /&*E."X. */
+ $"D27E DE80 0000 0009 BB69 0154 3C66 FD24" /* ~ހ...ƻi.TEijf' */
+ $"7A8A 6368 2F2C 3F2F 5D43 59ED 5E40 9E4F" /* zch/,?/]CY^@O */
+ $"59BB 6A03 B7F8 083A 021D F8F0 79AF 43B0" /* Yj..:..yC */
+ $"2270 F775 B8D8 FB59 371A 8B22 9F0B 55FA" /* "puY7.".U */
+ $"C9B1 5565 0914 AA67 2D9E 81B2 60FC 1333" /* ɱUe.g-`.3 */
+ $"318F 21E9 97E4 1400 F69E F709 7AE3 82A5" /* 1!..zゥ */
+ $"C9DE 7D16 1DF8 EDF8 FC5E DB3E FC2A F822" /* }..^>*" */
+ $"886E F247 9BC1 250E 905C 4AF7 4B22 3595" /* nG%.\JK"5 */
+ $"0835 2567 EEC1 C4C8 B70D D782 6A22 C9DA" /* .5%gȷ.ׂj" */
+ $"ADA3 0370 EED9 E428 E163 963A E6F0 5D4A" /* .p(c:]J */
+ $"F42A D11F B46D 7FBC 2D87 4274 4756 896E" /* *.m.-BtGVn */
+ $"C489 80C0 62F6 88A8 184A 79BE 3068 A9AF" /* ĉb.Jy0h */
+ $"D703 390B 1A76 3136 B799 726A AAA8 C23D" /* .9..v16rj= */
+ $"EEB0 E0F1 49E1 A6B7 204F 7BF3 6E44 F25E" /* Iᦷ O{nD^ */
+ $"7E0A 5125 CA89 B477 15CD 83CC CD21 C6BE" /* ~Q%ʉw.̓!ƾ */
+ $"F20B 99F3 C30C 5113 2E0A FF7E 1034 1BFF" /* ..Q..~.4. */
+ $"0378 72CF B0EE D132 2A2D 14A9 E37F 6B10" /* .xrϰ2*-..k. */
+ $"B5CE 0500 0000 0000 0000 0160 A7E3 A715" /* .........`. */
+ $"0C13 DE44 025B 9720 0000 0000 0000 0001" /* ..D.[ ........ */
+ $"E0F5 5A21 77FB C1F6 EAF0 244A EE78 B6FA" /* Z!w$Jx */
+ $"1769 3D98 AA28 2EB6 9826 A16B D6B8 BA89" /* .i=(.&kָ */
+ $"26C6 25C9 9E66 FD9C 6D2D FCDA FCB9 6AB2" /* &%ɞfm-j */
+ $"B42A 7008 5069 CB61 D530 B47A FCFF 7F95" /* *p.Pia0z. */
+ $"FA58 2D11 111A 53BF FB17 59E1 63E7 0FAD" /* X-...S.Yc. */
+ $"B016 A862 3107 8DF0 7112 DD6A B6E3 2477" /* .b1.q.j$w */
+ $"3162 94F0 6826 75B0 D0B8 4C13 21C7 E4BC" /* 1bh&uиL.! */
+ $"FB86 7A83 DF1C A4D9 C3B4 E3DE C334 1633" /* z.ô4.3 */
+ $"DC27 76B9 7ADF 50CA F95E C7C9 D5EB 402C" /* 'vzP^@, */
+ $"276F A0B0 D88B CF4A 0FD2 380A E3E7 2BA5" /* 'o؋J.8+ */
+ $"DD24 A247 B631 75B1 6D3A 4C60 9BAB E4C5" /* $G1um:L` */
+ $"BBCB 2972 36B9 AC74 5FF5 B5DB 231B 95F3" /* )r6t_#. */
+ $"7CFF 8E5F 86E9 1906 B3DF 75C7 0364 6192" /* |_..u.da */
+ $"A000 0000 0000 0000 0000 003B B1A0 0000" /* ..........;.. */
+ $"0000 0000 0000 0000 0000 0016 D51B CEEA" /* ............. */
+ $"0406 C003 4E50 C0B8 8DB2 F37C FF8E 5F86" /* ...NP|_ */
+ $"E919 06B3 DF75 C703 6461 92A0 0000 0000" /* ..u.da.... */
+ $"0000 0000 0000 3BB1 A000 0000 0000 0000" /* ......;....... */
+ $"0000 0000 0000 16D5 1BCE EA04 06C0 034E" /* ...........N */
+ $"50C0 B88D B2F4 4BED 0C57 2F64 38E7 DFE6" /* PK.W/d8 */
+ $"95DB 93EF B9ED 1250 6A6A A6E3 4478 1E7E" /* ۓ.PjjDx.~ */
+ $"F105 8E1B 961B 230C 9500 0000 0000 0C18" /* ...#........ */
+ $"0EA3 5F13 6306 E1B5 4B22 3D26 79A1 06E4" /* ._.c.K"=&y. */
+ $"228F F320 0B74 8820 0000 0000 0000 0007" /* " .t ........ */
+ $"9F62 443B 9A51 EAB0 7C1A A374 E7F1 52F3" /* bD;Q|.tR */
+ $"3BB8 C1E0 5919 6C04 A2E2 1032 6917 1394" /* ;Y.l..2i.. */
+ $"CCBE 472A 0652 C144 6D1E 755C ECF9 9725" /* ̾G*.RDm.u\% */
+ $"A84A 55AE 7A0E 6730 FCA7 B6E0 FF31 08C0" /* JUz.g01. */
+ $"3E76 4DDC 9E24 A60B C4A7 4CDD C221 1941" /* >vMܞ$.ħL!.A */
+ $"0810 2DE3 206F C551 4910 77AA 762A 1FF2" /* ..- oQI.wv*. */
+ $"D39A 0ADC 1089 90F8 D098 1E77 BDD1 AF74" /* Ӛ.И.wѯt */
+ $"4DEF D00D 0B2C CA2F 9EDC 27C4 35F9 F924" /* M..,/'5$ */
+ $"67FF 74E5 A746 B627 6267 F6D0 9B94 22D4" /* gtF'bgЛ" */
+ $"5FFE DF67 A7A9 E3CA 02A5 44D1 E926 DCA4" /* _g.D&ܤ */
+ $"7490 4AB8 E094 7037 FF7C 8CF7 2A9B B784" /* tJp7|* */
+ $"794E 9026 D48F FF7F F013 AABF F874 63DA" /* yN&ԏ..tc */
+ $"0302 FBFA AA6A 31D5 E2DF 85F7 2ACD D98C" /* ..j1߅*ٌ */
+ $"175D 1243 F1C0 CEA2 344C B6FC ADCF C6AA" /* .].C4Lƪ */
+ $"5CAE FA82 783F 58DD 754D 5C63 A018 19F8" /* \x?XuM\c.. */
+ $"7EFE 8953 AE16 22B9 4ED7 43D2 8A0B FA52" /* ~S."NCҊ.R */
+ $"5703 08CA 555B E51E 307B 0565 E165 22F3" /* W..U[.0{.ee" */
+ $"7130 0CEC CA23 576D 8D58 BED1 FEB6 9A2F" /* q0.#WmX/ */
+ $"DCEA 3288 3EEC B12A D43C B191 2627 7723" /* 2>*<&'w# */
+ $"F40F 1B7A F1E3 9A7E 366D 7490 EE80 3570" /* ..z~6mt5p */
+ $"CB59 D05D F384 AA96 71FE 68E8 4D32 F9D2" /* Y]qhM2 */
+ $"48BC 7B03 EA3B 879E 2E96 671F 8098 CE76" /* H{.;.g.v */
+ $"87D8 0336 5C88 91F4 3C69 D76B 4DEF 29DA" /* .6\`H {. */
+ $"BCA8 E157 DFD5 E879 C14C 1B1A 3431 2856" /* WyL..41(V */
+ $"0E74 D87F 7691 A923 632A 5EBC 6E95 6A46" /* .t.v#c*^njF */
+ $"8E68 7118 3704 6E41 37CB C9AC 7F84 1005" /* hq.7.nA7ɬ... */
+ $"C308 7C90 C6E0 B56C 8CF8 3C33 A97E C46C" /* .|l<3~l */
+ $"E373 91A9 4A3F 7331 481D 843F 0472 59DC" /* sJ?s1H.?.rY */
+ $"0837 85BA 4867 C202 6BFC 7433 F093 530F" /* .7Hg.kt3S. */
+ $"D721 8927 26CF AA6B B4E1 7981 FCE4 9F4E" /* !'&ϪkyN */
+ $"1105 9E7B 7F68 1CE7 D811 E270 FCA3 9CDE" /* ..{.h..p */
+ $"BFE6 48BA AC07 B55E 4E7F 5946 115B B1DC" /* H.^N.YF.[ */
+ $"F694 3A9B EADD 7971 E0F1 B6E1 C6FF 4337" /* :yqC7 */
+ $"F543 A074 FC74 AD1F 45BF 7867 C472 7605" /* Ctt.Exgrv. */
+ $"1786 E496 2D22 22BE 5760 9ACE A5C9 22B9" /* .-""W`Υ" */
+ $"9A14 4136 93E4 3E4A 715C 6F69 8447 5764" /* .A6>Jq\oiGWd */
+ $"D761 77A8 2C3F E200 ACAD 8331 11D0 ABE6" /* aw,?.1.Ы */
+ $"EA7B 5A22 58CF 097E 732F 894B E99C 6BAF" /* {Z"X~s/Kk */
+ $"B36C 68AA 5668 EF92 66E9 DDF9 FF78 DB89" /* lhVhfxۉ */
+ $"FC78 2032 99D9 62F0 1380 3FE8 41DF F341" /* x 2b.?AA */
+ $"F1FD 7F08 CE2E F09A 9335 6735 0FAD 8093" /* ...5g5. */
+ $"B6B1 53A2 964F EA7E 72A2 9A29 794D 98F0" /* SO~r)yM */
+ $"DD57 AE65 C61E DDEC BDB4 35C2 D050 E063" /* We.콴5Pc */
+ $"5F54 BCA5 928D 786D CAE3 8F96 BFEB 027E" /* _Txm㏖.~ */
+ $"6BBC 8795 7C88 1A2C 9908 36A1 6CAB 9268" /* k|.,.6lh */
+ $"EB0D 816F F34D 55F5 B562 BCFF 4190 EE42" /* .oMUbAB */
+ $"F195 E7F3 E220 7E51 74A2 FA23 23F7 20F0" /* ~Qt## */
+ $"985F 011C 34B8 C988 C519 C623 81BC D026" /* _..4Ɉ.#& */
+ $"A8C5 B82C 7A7F 86F7 E60E 44F2 CD53 6FAB" /* Ÿ,z..DSo */
+ $"2D69 1FD4 75B7 A011 80E7 09B3 B9CF 4503" /* -i.u.ƳE. */
+ $"F4A5 1DE3 AE8A 96F7 E245 0DF9 3859 24FA" /* .㮊E.8Y$ */
+ $"7B43 665F B15E D4D3 FB2A 666E FB6A 6CA8" /* {Cf_^*fnjl */
+ $"9CAF C052 D658 6DC3 BDD1 C752 21FA 6426" /* RXmýR!d& */
+ $"857C D05D 41C2 0241 306C 6FD4 81E3 69F7" /* |]A.A0loԁi */
+ $"A6F5 227F 39F9 780E 829A 17A9 C649 2BF5" /* ".9x..I+ */
+ $"01DD E500 A973 E599 890C A548 CE34 2877" /* ..s噉.H4(w */
+ $"D160 A2EE 4B3B CC7A 0C87 6723 51FD C222" /* `K;z.g#Q" */
+ $"9BFB E72D 2769 6250 2054 22C9 ABB2 3125" /* -'ibP T"ɫ1% */
+ $"7DA0 73A3 C083 1AAD 90F2 F0A8 C22D 9DF5" /* }s.- */
+ $"3FFB 6B56 18BE 61A6 B198 F1BC 1E4E 0CFE" /* ?kV.a.N. */
+ $"B323 8275 A644 CAC4 D36B 9AF9 FCC8 5DEC" /* #uDk] */
+ $"292B CC9B A0B6 9148 09DF E46D 1B2C 4EDF" /* )+̛Hm.,N */
+ $"EDB9 2DB4 BAD5 C50B 2115 C476 E09A F407" /* -.!.v. */
+ $"5EE8 2E9F 870F A319 32D4 EBEC CB7B FDE9" /* ^...2{ */
+ $"FB5C 0D41 61F3 BA97 0F9E 558F 6E39 C213" /* \.Aa.Un9. */
+ $"3101 A5E8 ECEC 15E5 4F29 F69A 58CD D82D" /* 1..O)X- */
+ $"986E E8E0 6898 17CC 98C4 C851 AC75 9306" /* nh.̘Qu. */
+ $"5241 0350 E2EB 38CA 1682 6132 FF45 578A" /* RA.P8.a2EW */
+ $"FA7F 7557 AC2C 9A18 3835 A62B 7D09 0992" /* .uW,.85+}ƒ */
+ $"EF78 BD49 5317 373A B07D 6420 DC30 5067" /* xIS.7:}d 0Pg */
+ $"A659 F316 4436 8E8E 9786 1163 A6A5 B841" /* Y.D6.cA */
+ $"99AA 4E1C 6163 309A 3C23 0208 FF4B 2967" /* N.ac0<#..K)g */
+ $"B7F7 37E4 A337 D0DC C48A A742 FB2D C529" /* 77ĊB-) */
+ $"BC6C 5BBE BC5C 03B1 F87F D7EA F5AC B94B" /* l[\..K */
+ $"5A88 84AB E44E BE6A F337 96B0 7CB6 1140" /* ZNj7|.@ */
+ $"25CB 45D6 BE02 A9D4 7868 1EDD F660 7963" /* %E־.xh.`yc */
+ $"B438 C030 3EA5 51FA F451 FA84 A6FF 2E5A" /* 80>QQ.Z */
+ $"EA95 2300 C0F4 39AA 42BA 4120 4EEC AFDD" /* #.9BA N */
+ $"20B4 6C42 0552 1177 6F1A AC6D FE8B 98E1" /* lB.R.wo.m */
+ $"D3FB F52E BCE3 5BB1 7235 F597 3E0D 8C46" /* .[r5>.F */
+ $"7361 1E2A C081 CA97 C8EA CD3F E91D 823F" /* sa.*ʗ?.? */
+ $"73A7 5EA1 F641 9CBD 219D D7E4 EE0D 270E" /* s^A!.'. */
+ $"14DF F756 B4E5 062C 8251 4AD0 B090 A0AF" /* .V.,QJа */
+ $"CD2A 3C80 6068 C9CD C3D7 3D7F 258B 365A" /* *<`h=.%6Z */
+ $"5E1A 90DC D058 4ED9 DF50 6F40 07A9 2BDE" /* ^.XNPo@.+ */
+ $"8486 5404 AB9C E323 0C4E D3DB 8352 0C0F" /* T.#.NۃR.. */
+ $"CD59 A5F5 56CC 0BB2 B603 85C3 C709 7D6E" /* YV..}n */
+ $"A10E 33C2 AE0F D52F 5AD7 2279 E6EB 7E6C" /* .3®./Z"y~l */
+ $"1757 589D C6DC B4FD 470C FFD9" /* .WXܴG. */
+};
+
diff --git a/linden/indra/newview/installers/darwin/firstlookslim-dmg/_DS_Store b/linden/indra/newview/installers/darwin/firstlookslim-dmg/_DS_Store
new file mode 100644
index 0000000..e170ac7
Binary files /dev/null and b/linden/indra/newview/installers/darwin/firstlookslim-dmg/_DS_Store differ
diff --git a/linden/indra/newview/installers/darwin/firstlookslim-dmg/_VolumeIcon.icns b/linden/indra/newview/installers/darwin/firstlookslim-dmg/_VolumeIcon.icns
new file mode 100644
index 0000000..da5307e
Binary files /dev/null and b/linden/indra/newview/installers/darwin/firstlookslim-dmg/_VolumeIcon.icns differ
diff --git a/linden/indra/newview/installers/darwin/firstlookslim-dmg/background.jpg b/linden/indra/newview/installers/darwin/firstlookslim-dmg/background.jpg
new file mode 100644
index 0000000..55294dc
Binary files /dev/null and b/linden/indra/newview/installers/darwin/firstlookslim-dmg/background.jpg differ
diff --git a/linden/indra/newview/linux_tools/client-readme.txt b/linden/indra/newview/linux_tools/client-readme.txt
index 179cb9e..fc7994b 100644
--- a/linden/indra/newview/linux_tools/client-readme.txt
+++ b/linden/indra/newview/linux_tools/client-readme.txt
@@ -15,7 +15,7 @@ Life itself - please see .
5.3. Blank window after minimizing it
5.4. Audio
5.5. 'Alt' key for camera controls doesn't work
- 5.6. In-world movie playback
+ 5.6. In-world streaming movie/music playback
6. Advanced Troubleshooting
6.1. Audio
6.2. OpenGL
@@ -169,11 +169,11 @@ SOLUTION:- Some window managers eat the Alt key for their own purposes; you
example, the 'Windows' key!) which will allow the Alt key to function
properly with mouse actions in Second Life and other applications.
-PROBLEM 6:- In-world movie playback doesn't work for me.
+PROBLEM 6:- In-world movie and/or music playback doesn't work for me.
SOLUTION:- You need to have a working installation of GStreamer 0.10; this
is usually an optional package for most versions of Linux. If you have
- installed GStreamer 0.10 and you can play some movies but not others then
- you need to install a wider selection of GStreamer plugins, either
+ installed GStreamer 0.10 and you can play some music/movies but not others
+ then you need to install a wider selection of GStreamer plugins, either
from your vendor or an appropriate third party.
@@ -183,11 +183,11 @@ SOLUTION:- You need to have a working installation of GStreamer 0.10; this
The 'secondlife' script which launches Second Life contains some
configuration options for advanced troubleshooters.
-* AUDIO - Edit the 'secondlife' script and you will see three audio
- options: LL_BAD_ESD, LL_BAD_OSS, LL_BAD_ALSA. Second Life tries to
- use ESD, OSS, then ALSA audio drivers in this order; you may uncomment
- the corresponding LL_BAD_* option to skip an audio driver which you
- believe may be causing you trouble.
+* AUDIO - Edit the 'secondlife' script and you will see these audio
+ options: LL_BAD_OPENAL_DRIVER, LL_BAD_FMOD_ESD, LL_BAD_FMOD_OSS, and
+ LL_BAD_FMOD_ALSA. Second Life tries to use OpenAL, ESD, OSS, then ALSA
+ audio drivers in this order; you may uncomment the corresponding LL_BAD_*
+ option to skip an audio driver which you believe may be causing you trouble.
* OPENGL - For advanced troubleshooters, the LL_GL_BLACKLIST option lets
you disable specific GL extensions, each of which is represented by a
diff --git a/linden/indra/newview/linux_tools/wrapper.sh b/linden/indra/newview/linux_tools/wrapper.sh
index 9d2e06b..d7b17ed 100755
--- a/linden/indra/newview/linux_tools/wrapper.sh
+++ b/linden/indra/newview/linux_tools/wrapper.sh
@@ -4,14 +4,17 @@
## These options are for self-assisted troubleshooting during this beta
## testing phase; you should not usually need to touch them.
-## - Avoids using the ESD audio driver.
-#export LL_BAD_ESD=x
-
-## - Avoids using the OSS audio driver.
-#export LL_BAD_OSS=x
-
-## - Avoids using the ALSA audio driver.
-#export LL_BAD_ALSA=x
+## - Avoids using any OpenAL audio driver.
+#export LL_BAD_OPENAL_DRIVER=x
+## - Avoids using any FMOD audio driver.
+#export LL_BAD_FMOD_DRIVER=x
+
+## - Avoids using the FMOD ESD audio driver.
+#export LL_BAD_FMOD_ESD=x
+## - Avoids using the FMOD OSS audio driver.
+#export LL_BAD_FMOD_OSS=x
+## - Avoids using the FMOD ALSA audio driver.
+#export LL_BAD_FMOD_ALSA=x
## - Avoids the optional OpenGL extensions which have proven most problematic
## on some hardware. Disabling this option may cause BETTER PERFORMANCE but
diff --git a/linden/indra/newview/llappviewer.cpp b/linden/indra/newview/llappviewer.cpp
index e1a0fe2..09d1141 100644
--- a/linden/indra/newview/llappviewer.cpp
+++ b/linden/indra/newview/llappviewer.cpp
@@ -1156,16 +1156,24 @@ bool LLAppViewer::cleanup()
llinfos << "Global stuff deleted" << llendflush;
-#if !LL_RELEASE_FOR_DOWNLOAD
if (gAudiop)
{
- gAudiop->shutdown();
+ bool want_longname = false;
+ if (gAudiop->getDriverName(want_longname) == "FMOD")
+ {
+ // This hack exists because fmod likes to occasionally
+ // hang forever when shutting down, for no apparent
+ // reason.
+ llwarns << "Hack, skipping FMOD audio engine cleanup" << llendflush;
+ }
+ else
+ {
+ gAudiop->shutdown();
+ }
+
+ delete gAudiop;
+ gAudiop = NULL;
}
-#else
- // This hack exists because fmod likes to occasionally hang forever
- // when shutting down for no apparent reason.
- llwarns << "Hack, skipping audio engine cleanup" << llendflush;
-#endif
// Note: this is where LLFeatureManager::getInstance()-> used to be deleted.
@@ -1176,9 +1184,6 @@ bool LLAppViewer::cleanup()
cleanupSavedSettings();
llinfos << "Settings patched up" << llendflush;
- delete gAudiop;
- gAudiop = NULL;
-
// delete some of the files left around in the cache.
removeCacheFiles("*.wav");
removeCacheFiles("*.tmp");
@@ -2301,6 +2306,7 @@ void LLAppViewer::handleViewerCrash()
gDebugInfo["CurrentPath"] = gDirUtilp->getCurPath();
gDebugInfo["SessionLength"] = F32(LLFrameTimer::getElapsedSeconds());
gDebugInfo["StartupState"] = LLStartUp::getStartupStateString();
+ gDebugInfo["RAMInfo"]["Allocated"] = (LLSD::Integer) getCurrentRSS() >> 10;
if(gLogoutInProgress)
{
@@ -3206,6 +3212,8 @@ void LLAppViewer::idle()
return;
}
+ gViewerWindow->handlePerFrameHover();
+
///////////////////////////////////////
// Agent and camera movement
//
diff --git a/linden/indra/newview/llfloaterabout.cpp b/linden/indra/newview/llfloaterabout.cpp
index a5ad97f..19b7653 100644
--- a/linden/indra/newview/llfloaterabout.cpp
+++ b/linden/indra/newview/llfloaterabout.cpp
@@ -41,6 +41,7 @@
#include "llcurl.h"
#include "llimagej2c.h"
+#include "audioengine.h"
#include "llviewertexteditor.h"
#include "llviewercontrol.h"
@@ -197,6 +198,11 @@ LLFloaterAbout::LLFloaterAbout()
support.append( LLImageJ2C::getEngineInfo() );
support.append("\n");
+ support.append("Audio Driver Version: ");
+ bool want_fullname = true;
+ support.append( gAudiop ? gAudiop->getDriverName(want_fullname) : "(none)" );
+ support.append("\n");
+
LLMediaManager *mgr = LLMediaManager::getInstance();
if (mgr)
{
diff --git a/linden/indra/newview/llfloateractivespeakers.cpp b/linden/indra/newview/llfloateractivespeakers.cpp
index be0e88f..0a19fb2 100644
--- a/linden/indra/newview/llfloateractivespeakers.cpp
+++ b/linden/indra/newview/llfloateractivespeakers.cpp
@@ -547,7 +547,8 @@ void LLPanelActiveSpeakers::refreshSpeakers()
&& gVoiceClient->getVoiceEnabled(selected_id)
&& selected_id.notNull()
&& selected_id != gAgent.getID()
- && (selected_speakerp.notNull() && selected_speakerp->mType == LLSpeaker::SPEAKER_AGENT));
+ && (selected_speakerp.notNull() && (selected_speakerp->mType == LLSpeaker::SPEAKER_AGENT || selected_speakerp->mType == LLSpeaker::SPEAKER_EXTERNAL)));
+
}
if (mMuteTextCtrl)
{
@@ -555,6 +556,7 @@ void LLPanelActiveSpeakers::refreshSpeakers()
mMuteTextCtrl->setEnabled(selected_id.notNull()
&& selected_id != gAgent.getID()
&& selected_speakerp.notNull()
+ && selected_speakerp->mType != LLSpeaker::SPEAKER_EXTERNAL
&& !LLMuteList::getInstance()->isLinden(selected_speakerp->mDisplayName));
}
childSetValue("speaker_volume", gVoiceClient->getUserVolume(selected_id));
@@ -562,7 +564,7 @@ void LLPanelActiveSpeakers::refreshSpeakers()
&& gVoiceClient->getVoiceEnabled(selected_id)
&& selected_id.notNull()
&& selected_id != gAgent.getID()
- && (selected_speakerp.notNull() && selected_speakerp->mType == LLSpeaker::SPEAKER_AGENT));
+ && (selected_speakerp.notNull() && (selected_speakerp->mType == LLSpeaker::SPEAKER_AGENT || selected_speakerp->mType == LLSpeaker::SPEAKER_EXTERNAL)));
childSetEnabled(
"moderator_controls_label",
@@ -580,7 +582,7 @@ void LLPanelActiveSpeakers::refreshSpeakers()
if (mProfileBtn)
{
- mProfileBtn->setEnabled(selected_id.notNull());
+ mProfileBtn->setEnabled(selected_id.notNull() && (selected_speakerp.notNull() && selected_speakerp->mType != LLSpeaker::SPEAKER_EXTERNAL) );
}
// show selected user name in large font
@@ -1034,9 +1036,17 @@ void LLSpeakerMgr::update(BOOL resort_ok)
// speaker no longer registered in voice channel, demote to text only
else if (speakerp->mStatus != LLSpeaker::STATUS_NOT_IN_CHANNEL)
{
- speakerp->mStatus = LLSpeaker::STATUS_TEXT_ONLY;
- speakerp->mSpeechVolume = 0.f;
- speakerp->mDotColor = ACTIVE_COLOR;
+ if(speakerp->mType == LLSpeaker::SPEAKER_EXTERNAL)
+ {
+ // external speakers should be timed out when they leave the voice channel (since they only exist via SLVoice)
+ speakerp->mStatus = LLSpeaker::STATUS_NOT_IN_CHANNEL;
+ }
+ else
+ {
+ speakerp->mStatus = LLSpeaker::STATUS_TEXT_ONLY;
+ speakerp->mSpeechVolume = 0.f;
+ speakerp->mDotColor = ACTIVE_COLOR;
+ }
}
}
@@ -1088,13 +1098,16 @@ void LLSpeakerMgr::updateSpeakerList()
if ((!mVoiceChannel && gVoiceClient->inProximalChannel()) || (mVoiceChannel && mVoiceChannel->isActive()))
{
LLVoiceClient::participantMap* participants = gVoiceClient->getParticipantList();
- LLVoiceClient::participantMap::iterator participant_it;
-
- // add new participants to our list of known speakers
- for (participant_it = participants->begin(); participant_it != participants->end(); ++participant_it)
+ if(participants)
{
- LLVoiceClient::participantState* participantp = participant_it->second;
- setSpeaker(participantp->mAvatarID, LLStringUtil::null, LLSpeaker::STATUS_VOICE_ACTIVE);
+ LLVoiceClient::participantMap::iterator participant_it;
+
+ // add new participants to our list of known speakers
+ for (participant_it = participants->begin(); participant_it != participants->end(); ++participant_it)
+ {
+ LLVoiceClient::participantState* participantp = participant_it->second;
+ setSpeaker(participantp->mAvatarID, participantp->mDisplayName, LLSpeaker::STATUS_VOICE_ACTIVE, (participantp->mAvatarIDValid?LLSpeaker::SPEAKER_AGENT:LLSpeaker::SPEAKER_EXTERNAL));
+ }
}
}
}
@@ -1167,6 +1180,10 @@ void LLIMSpeakerMgr::updateSpeakerList()
{
// don't do normal updates which are pulled from voice channel
// rely on user list reported by sim
+
+ // We need to do this to allow PSTN callers into group chats to show in the list.
+ LLSpeakerMgr::updateSpeakerList();
+
return;
}
diff --git a/linden/indra/newview/llfloateractivespeakers.h b/linden/indra/newview/llfloateractivespeakers.h
index 9b6f119..0fb0267 100644
--- a/linden/indra/newview/llfloateractivespeakers.h
+++ b/linden/indra/newview/llfloateractivespeakers.h
@@ -53,7 +53,8 @@ public:
typedef enum e_speaker_type
{
SPEAKER_AGENT,
- SPEAKER_OBJECT
+ SPEAKER_OBJECT,
+ SPEAKER_EXTERNAL // Speaker that doesn't map to an avatar or object (i.e. PSTN caller in a group)
} ESpeakerType;
typedef enum e_speaker_status
diff --git a/linden/indra/newview/llfloaterfriends.cpp b/linden/indra/newview/llfloaterfriends.cpp
index 5563705..1402b2c 100644
--- a/linden/indra/newview/llfloaterfriends.cpp
+++ b/linden/indra/newview/llfloaterfriends.cpp
@@ -57,6 +57,7 @@
#include "llviewermessage.h"
#include "lltimer.h"
#include "lltextbox.h"
+#include "llvoiceclient.h"
//Maximum number of people you can select to do an operation on at once.
#define MAX_FRIEND_SELECT 20
@@ -64,6 +65,8 @@
#define RIGHTS_CHANGE_TIMEOUT 5.0
#define OBSERVER_TIMEOUT 0.5
+#define ONLINE_SIP_ICON_NAME "slim_icon_16_viewer.tga"
+
// simple class to observe the calling cards.
class LLLocalFriendsObserver : public LLFriendObserver, public LLEventTimer
{
@@ -111,10 +114,14 @@ LLPanelFriends::LLPanelFriends() :
mEventTimer.stop();
mObserver = new LLLocalFriendsObserver(this);
LLAvatarTracker::instance().addObserver(mObserver);
+ // For notification when SIP online status changes.
+ LLVoiceClient::getInstance()->addObserver(mObserver);
}
LLPanelFriends::~LLPanelFriends()
{
+ // For notification when SIP online status changes.
+ LLVoiceClient::getInstance()->removeObserver(mObserver);
LLAvatarTracker::instance().removeObserver(mObserver);
delete mObserver;
}
@@ -212,7 +219,9 @@ BOOL LLPanelFriends::addFriend(const LLUUID& agent_id)
LLAvatarTracker& at = LLAvatarTracker::instance();
const LLRelationship* relationInfo = at.getBuddyInfo(agent_id);
if(!relationInfo) return FALSE;
- BOOL online = relationInfo->isOnline();
+
+ bool isOnlineSIP = LLVoiceClient::getInstance()->isOnlineSIP(agent_id);
+ bool isOnline = relationInfo->isOnline();
std::string fullname;
BOOL have_name = gCacheName->getFullName(agent_id, fullname);
@@ -228,12 +237,17 @@ BOOL LLPanelFriends::addFriend(const LLUUID& agent_id)
LLSD& online_status_column = element["columns"][LIST_ONLINE_STATUS];
online_status_column["column"] = "icon_online_status";
online_status_column["type"] = "icon";
-
- if (online)
+
+ if (isOnline)
{
friend_column["font-style"] = "BOLD";
online_status_column["value"] = "icon_avatar_online.tga";
}
+ else if(isOnlineSIP)
+ {
+ friend_column["font-style"] = "BOLD";
+ online_status_column["value"] = ONLINE_SIP_ICON_NAME;
+ }
LLSD& online_column = element["columns"][LIST_VISIBLE_ONLINE];
online_column["column"] = "icon_visible_online";
@@ -271,14 +285,30 @@ BOOL LLPanelFriends::updateFriendItem(const LLUUID& agent_id, const LLRelationsh
if (!info) return FALSE;
LLScrollListItem* itemp = mFriendsList->getItem(agent_id);
if (!itemp) return FALSE;
+
+ bool isOnlineSIP = LLVoiceClient::getInstance()->isOnlineSIP(itemp->getUUID());
+ bool isOnline = info->isOnline();
std::string fullname;
BOOL have_name = gCacheName->getFullName(agent_id, fullname);
+
+ // Name of the status icon to use
+ std::string statusIcon;
+
+ if(isOnline)
+ {
+ statusIcon = "icon_avatar_online.tga";
+ }
+ else if(isOnlineSIP)
+ {
+ statusIcon = ONLINE_SIP_ICON_NAME;
+ }
- itemp->getColumn(LIST_ONLINE_STATUS)->setValue(info->isOnline() ? std::string("icon_avatar_online.tga") : LLStringUtil::null);
+ itemp->getColumn(LIST_ONLINE_STATUS)->setValue(statusIcon);
+
itemp->getColumn(LIST_FRIEND_NAME)->setValue(fullname);
// render name of online friends in bold text
- ((LLScrollListText*)itemp->getColumn(LIST_FRIEND_NAME))->setFontStyle(info->isOnline() ? LLFontGL::BOLD : LLFontGL::NORMAL);
+ ((LLScrollListText*)itemp->getColumn(LIST_FRIEND_NAME))->setFontStyle((isOnline || isOnlineSIP) ? LLFontGL::BOLD : LLFontGL::NORMAL);
itemp->getColumn(LIST_VISIBLE_ONLINE)->setValue(info->isRightGrantedTo(LLRelationship::GRANT_ONLINE_STATUS));
itemp->getColumn(LIST_VISIBLE_MAP)->setValue(info->isRightGrantedTo(LLRelationship::GRANT_MAP_LOCATION));
itemp->getColumn(LIST_EDIT_MINE)->setValue(info->isRightGrantedTo(LLRelationship::GRANT_MODIFY_OBJECTS));
diff --git a/linden/indra/newview/llimpanel.cpp b/linden/indra/newview/llimpanel.cpp
index 0c9333e..6d77354 100644
--- a/linden/indra/newview/llimpanel.cpp
+++ b/linden/indra/newview/llimpanel.cpp
@@ -357,7 +357,7 @@ LLVoiceChannel::LLVoiceChannel(const LLUUID& session_id, const std::string& sess
llwarns << "Duplicate voice channels registered for session_id " << session_id << llendl;
}
- LLVoiceClient::getInstance()->addStatusObserver(this);
+ LLVoiceClient::getInstance()->addObserver(this);
}
LLVoiceChannel::~LLVoiceChannel()
@@ -365,7 +365,7 @@ LLVoiceChannel::~LLVoiceChannel()
// Don't use LLVoiceClient::getInstance() here -- this can get called during atexit() time and that singleton MAY have already been destroyed.
if(gVoiceClient)
{
- gVoiceClient->removeStatusObserver(this);
+ gVoiceClient->removeObserver(this);
}
sVoiceChannelMap.erase(mSessionID);
@@ -983,7 +983,8 @@ void LLVoiceChannelP2P::activate()
// otherwise answering the call
else
{
- LLVoiceClient::getInstance()->answerInvite(mSessionHandle, mOtherUserID);
+ LLVoiceClient::getInstance()->answerInvite(mSessionHandle);
+
// using the session handle invalidates it. Clear it out here so we can't reuse it by accident.
mSessionHandle.clear();
}
@@ -1000,7 +1001,7 @@ void LLVoiceChannelP2P::getChannelInfo()
}
// receiving session from other user who initiated call
-void LLVoiceChannelP2P::setSessionHandle(const std::string& handle)
+void LLVoiceChannelP2P::setSessionHandle(const std::string& handle, const std::string &inURI)
{
BOOL needs_activate = FALSE;
if (callStarted())
@@ -1023,8 +1024,17 @@ void LLVoiceChannelP2P::setSessionHandle(const std::string& handle)
}
mSessionHandle = handle;
+
// The URI of a p2p session should always be the other end's SIP URI.
- setURI(LLVoiceClient::getInstance()->sipURIFromID(mOtherUserID));
+ if(!inURI.empty())
+ {
+ setURI(inURI);
+ }
+ else
+ {
+ setURI(LLVoiceClient::getInstance()->sipURIFromID(mOtherUserID));
+ }
+
mReceivedCall = TRUE;
if (needs_activate)
@@ -1207,7 +1217,23 @@ LLFloaterIMPanel::~LLFloaterIMPanel()
{
delete mSpeakers;
mSpeakers = NULL;
-
+
+ // End the text IM session if necessary
+ if(gVoiceClient && mOtherParticipantUUID.notNull())
+ {
+ switch(mDialog)
+ {
+ case IM_NOTHING_SPECIAL:
+ case IM_SESSION_P2P_INVITE:
+ gVoiceClient->endUserIMSession(mOtherParticipantUUID);
+ break;
+
+ default:
+ // Appease the compiler
+ break;
+ }
+ }
+
//kicks you out of the voice channel if it is currently active
// HAVE to do this here -- if it happens in the LLVoiceChannel destructor it will call the wrong version (since the object's partially deconstructed at that point).
@@ -1872,33 +1898,45 @@ void deliver_message(const std::string& utf8_text,
EInstantMessage dialog)
{
std::string name;
+ bool sent = false;
gAgent.buildFullname(name);
const LLRelationship* info = NULL;
info = LLAvatarTracker::instance().getBuddyInfo(other_participant_id);
+
U8 offline = (!info || info->isOnline()) ? IM_ONLINE : IM_OFFLINE;
-
- // default to IM_SESSION_SEND unless it's nothing special - in
- // which case it's probably an IM to everyone.
- U8 new_dialog = dialog;
-
- if ( dialog != IM_NOTHING_SPECIAL )
+
+ if((offline == IM_OFFLINE) && (LLVoiceClient::getInstance()->isOnlineSIP(other_participant_id)))
{
- new_dialog = IM_SESSION_SEND;
+ // User is online through the OOW connector, but not with a regular viewer. Try to send the message via SLVoice.
+ sent = gVoiceClient->sendTextMessage(other_participant_id, utf8_text);
}
+
+ if(!sent)
+ {
+ // Send message normally.
- pack_instant_message(
- gMessageSystem,
- gAgent.getID(),
- FALSE,
- gAgent.getSessionID(),
- other_participant_id,
- name,
- utf8_text,
- offline,
- (EInstantMessage)new_dialog,
- im_session_id);
- gAgent.sendReliableMessage();
+ // default to IM_SESSION_SEND unless it's nothing special - in
+ // which case it's probably an IM to everyone.
+ U8 new_dialog = dialog;
+
+ if ( dialog != IM_NOTHING_SPECIAL )
+ {
+ new_dialog = IM_SESSION_SEND;
+ }
+ pack_instant_message(
+ gMessageSystem,
+ gAgent.getID(),
+ FALSE,
+ gAgent.getSessionID(),
+ other_participant_id,
+ name.c_str(),
+ utf8_text.c_str(),
+ offline,
+ (EInstantMessage)new_dialog,
+ im_session_id);
+ gAgent.sendReliableMessage();
+ }
// If there is a mute list and this is not a group chat...
if ( LLMuteList::getInstance() )
diff --git a/linden/indra/newview/llimpanel.h b/linden/indra/newview/llimpanel.h
index bdddda4..3e22292 100644
--- a/linden/indra/newview/llimpanel.h
+++ b/linden/indra/newview/llimpanel.h
@@ -161,7 +161,7 @@ public:
/*virtual*/ void activate();
/*virtual*/ void getChannelInfo();
- void setSessionHandle(const std::string& handle);
+ void setSessionHandle(const std::string& handle, const std::string &inURI);
protected:
virtual void setState(EState state);
@@ -294,8 +294,6 @@ private:
void sendTypingState(BOOL typing);
- static LLFloaterIMPanel* sInstance;
-
private:
LLLineEditor* mInputEditor;
LLViewerTextEditor* mHistoryEditor;
diff --git a/linden/indra/newview/llimview.cpp b/linden/indra/newview/llimview.cpp
index 7839146..10e8ff8 100644
--- a/linden/indra/newview/llimview.cpp
+++ b/linden/indra/newview/llimview.cpp
@@ -267,7 +267,8 @@ public:
EInstantMessage type,
EInvitationType inv_type,
const std::string& session_handle,
- const std::string& notify_box) :
+ const std::string& notify_box,
+ const std::string& session_uri) :
mSessionID(session_id),
mSessionName(session_name),
mCallerID(caller_id),
@@ -275,7 +276,8 @@ public:
mType(type),
mInvType(inv_type),
mSessionHandle(session_handle),
- mNotifyBox(notify_box)
+ mNotifyBox(notify_box),
+ mSessionURI(session_uri)
{};
LLUUID mSessionID;
@@ -286,6 +288,7 @@ public:
EInvitationType mInvType;
std::string mSessionHandle;
std::string mNotifyBox;
+ std::string mSessionURI;
};
@@ -568,7 +571,8 @@ BOOL LLIMMgr::isIMSessionOpen(const LLUUID& uuid)
LLUUID LLIMMgr::addP2PSession(const std::string& name,
const LLUUID& other_participant_id,
- const std::string& voice_session_handle)
+ const std::string& voice_session_handle,
+ const std::string& caller_uri)
{
LLUUID session_id = addSession(name, IM_NOTHING_SPECIAL, other_participant_id);
@@ -576,7 +580,7 @@ LLUUID LLIMMgr::addP2PSession(const std::string& name,
if(floater)
{
LLVoiceChannelP2P* voice_channelp = (LLVoiceChannelP2P*)floater->getVoiceChannel();
- voice_channelp->setSessionHandle(voice_session_handle);
+ voice_channelp->setSessionHandle(voice_session_handle, caller_uri);
}
return session_id;
@@ -699,7 +703,8 @@ void LLIMMgr::inviteToSession(
const std::string& caller_name,
EInstantMessage type,
EInvitationType inv_type,
- const std::string& session_handle)
+ const std::string& session_handle,
+ const std::string& session_uri)
{
//ignore invites from muted residents
if (LLMuteList::getInstance()->isMuted(caller_id))
@@ -741,7 +746,8 @@ void LLIMMgr::inviteToSession(
type,
inv_type,
session_handle,
- notify_box_type);
+ notify_box_type,
+ session_uri);
LLVoiceChannel* channelp = LLVoiceChannel::getChannelByID(session_id);
if (channelp && channelp->callStarted())
@@ -916,7 +922,8 @@ void LLIMMgr::inviteUserResponse(S32 option, void* user_data)
invitep->mSessionID = gIMMgr->addP2PSession(
invitep->mSessionName,
invitep->mCallerID,
- invitep->mSessionHandle);
+ invitep->mSessionHandle,
+ invitep->mSessionURI );
LLFloaterIMPanel* im_floater =
gIMMgr->findFloaterBySession(
diff --git a/linden/indra/newview/llimview.h b/linden/indra/newview/llimview.h
index d28d00a..6b82c2e 100644
--- a/linden/indra/newview/llimview.h
+++ b/linden/indra/newview/llimview.h
@@ -97,7 +97,8 @@ public:
// Creates a P2P session with the requisite handle for responding to voice calls
LLUUID addP2PSession(const std::string& name,
const LLUUID& other_participant_id,
- const std::string& voice_session_handle);
+ const std::string& voice_session_handle,
+ const std::string& caller_uri = LLStringUtil::null);
// This removes the panel referenced by the uuid, and then
// restores internal consistency. The internal pointer is not
@@ -111,7 +112,8 @@ public:
const std::string& caller_name,
EInstantMessage type,
EInvitationType inv_type,
- const std::string& session_handle = LLStringUtil::null);
+ const std::string& session_handle = LLStringUtil::null,
+ const std::string& session_uri = LLStringUtil::null);
//Updates a given session's session IDs. Does not open,
//create or do anything new. If the old session doesn't
diff --git a/linden/indra/newview/llstartup.cpp b/linden/indra/newview/llstartup.cpp
index dd4b66c..5f25dc3 100644
--- a/linden/indra/newview/llstartup.cpp
+++ b/linden/indra/newview/llstartup.cpp
@@ -45,6 +45,10 @@
# include "audioengine_fmod.h"
#endif
+#ifdef LL_OPENAL
+#include "audioengine_openal.h"
+#endif
+
#include "llares.h"
#include "llcachename.h"
#include "llviewercontrol.h"
@@ -581,10 +585,28 @@ bool idle_startup()
if (FALSE == gSavedSettings.getBOOL("NoAudio"))
{
-#ifdef LL_FMOD
- gAudiop = (LLAudioEngine *) new LLAudioEngine_FMOD();
-#else
gAudiop = NULL;
+
+#ifdef LL_OPENAL
+ if (!gAudiop
+#if !LL_WINDOWS
+ && NULL == getenv("LL_BAD_OPENAL_DRIVER")
+#endif // !LL_WINDOWS
+ )
+ {
+ gAudiop = (LLAudioEngine *) new LLAudioEngine_OpenAL();
+ }
+#endif
+
+#ifdef LL_FMOD
+ if (!gAudiop
+#if !LL_WINDOWS
+ && NULL == getenv("LL_BAD_FMOD_DRIVER")
+#endif // !LL_WINDOWS
+ )
+ {
+ gAudiop = (LLAudioEngine *) new LLAudioEngine_FMOD();
+ }
#endif
if (gAudiop)
@@ -597,11 +619,16 @@ bool idle_startup()
void* window_handle = NULL;
#endif
bool init = gAudiop->init(kAUDIO_NUM_SOURCES, window_handle);
- if(!init)
+ if(init)
+ {
+ gAudiop->setMuted(TRUE);
+ }
+ else
{
LL_WARNS("AppInit") << "Unable to initialize audio engine" << LL_ENDL;
+ delete gAudiop;
+ gAudiop = NULL;
}
- gAudiop->setMuted(TRUE);
}
}
diff --git a/linden/indra/newview/llviewercontrol.cpp b/linden/indra/newview/llviewercontrol.cpp
index 210fca4..df39ee7 100644
--- a/linden/indra/newview/llviewercontrol.cpp
+++ b/linden/indra/newview/llviewercontrol.cpp
@@ -552,7 +552,6 @@ void settings_setup_listeners()
gSavedSettings.getControl("PushToTalkButton")->getSignal()->connect(boost::bind(&handleVoiceClientPrefsChanged, _1));
gSavedSettings.getControl("PushToTalkToggle")->getSignal()->connect(boost::bind(&handleVoiceClientPrefsChanged, _1));
gSavedSettings.getControl("VoiceEarLocation")->getSignal()->connect(boost::bind(&handleVoiceClientPrefsChanged, _1));
- gSavedSettings.getControl("VivoxDebugServerName")->getSignal()->connect(boost::bind(&handleVoiceClientPrefsChanged, _1));
gSavedSettings.getControl("VoiceInputAudioDevice")->getSignal()->connect(boost::bind(&handleVoiceClientPrefsChanged, _1));
gSavedSettings.getControl("VoiceOutputAudioDevice")->getSignal()->connect(boost::bind(&handleVoiceClientPrefsChanged, _1));
gSavedSettings.getControl("AudioLevelMic")->getSignal()->connect(boost::bind(&handleVoiceClientPrefsChanged, _1));
diff --git a/linden/indra/newview/llviewerdisplay.cpp b/linden/indra/newview/llviewerdisplay.cpp
index a4438a5..96591a2 100644
--- a/linden/indra/newview/llviewerdisplay.cpp
+++ b/linden/indra/newview/llviewerdisplay.cpp
@@ -253,8 +253,6 @@ void display(BOOL rebuild, F32 zoom_factor, int subfield, BOOL for_snapshot)
gViewerWindow->performPick();
}
- gViewerWindow->handlePerFrameHover();
-
LLAppViewer::instance()->pingMainloopTimeout("Display:CheckStates");
LLGLState::checkStates();
LLGLState::checkTextureChannels();
diff --git a/linden/indra/newview/llvoiceclient.cpp b/linden/indra/newview/llvoiceclient.cpp
index 3bec16f..145132a 100644
--- a/linden/indra/newview/llvoiceclient.cpp
+++ b/linden/indra/newview/llvoiceclient.cpp
@@ -62,15 +62,20 @@
#include "llviewerwindow.h"
#include "llviewercamera.h"
+#include "llfloaterfriends.h" //VIVOX, inorder to refresh communicate panel
+#include "llfloaterchat.h" // for LLFloaterChat::addChat()
+
// for base64 decoding
#include "apr_base64.h"
// for SHA1 hash
#include "apr_sha1.h"
-// If we are connecting to agni AND the user's last name is "Linden", join this channel instead of looking up the sim name.
-// If we are connecting to agni and the user's last name is NOT "Linden", disable voice.
-#define AGNI_LINDENS_ONLY_CHANNEL "SL"
+// for MD5 hash
+#include "llmd5.h"
+
+#define USE_SESSION_GROUPS 0
+
static bool sConnectingToAgni = false;
F32 LLVoiceClient::OVERDRIVEN_POWER_LEVEL = 0.7f;
@@ -90,6 +95,44 @@ const F32 UPDATE_THROTTLE_SECONDS = 0.1f;
const F32 LOGIN_RETRY_SECONDS = 10.0f;
const int MAX_LOGIN_RETRIES = 12;
+static void setUUIDFromStringHash(LLUUID &uuid, const std::string &str)
+{
+ LLMD5 md5_uuid;
+ md5_uuid.update((const unsigned char*)str.data(), str.size());
+ md5_uuid.finalize();
+ md5_uuid.raw_digest(uuid.mData);
+}
+
+static int scale_mic_volume(float volume)
+{
+ // incoming volume has the range [0.0 ... 2.0], with 1.0 as the default.
+ // Map it as follows: 0.0 -> 40, 1.0 -> 44, 2.0 -> 75
+
+ volume -= 1.0f; // offset volume to the range [-1.0 ... 1.0], with 0 at the default.
+ int scaled_volume = 44; // offset scaled_volume by its default level
+ if(volume < 0.0f)
+ scaled_volume += ((int)(volume * 4.0f)); // (44 - 40)
+ else
+ scaled_volume += ((int)(volume * 31.0f)); // (75 - 44)
+
+ return scaled_volume;
+}
+
+static int scale_speaker_volume(float volume)
+{
+ // incoming volume has the range [0.0 ... 1.0], with 0.5 as the default.
+ // Map it as follows: 0.0 -> 0, 0.5 -> 62, 1.0 -> 75
+
+ volume -= 0.5f; // offset volume to the range [-0.5 ... 0.5], with 0 at the default.
+ int scaled_volume = 62; // offset scaled_volume by its default level
+ if(volume < 0.0f)
+ scaled_volume += ((int)(volume * 124.0f)); // (62 - 0) * 2
+ else
+ scaled_volume += ((int)(volume * 26.0f)); // (75 - 62) * 2
+
+ return scaled_volume;
+}
+
class LLViewerVoiceAccountProvisionResponder :
public LLHTTPClient::Responder
{
@@ -103,12 +146,13 @@ public:
{
if ( mRetries > 0 )
{
+ LL_WARNS("Voice") << "ProvisionVoiceAccountRequest returned an error, retrying. status = " << status << ", reason = \"" << reason << "\"" << LL_ENDL;
if ( gVoiceClient ) gVoiceClient->requestVoiceAccountProvision(
mRetries - 1);
}
else
{
- //TODO: throw an error message?
+ LL_WARNS("Voice") << "ProvisionVoiceAccountRequest returned an error, too many retries (giving up). status = " << status << ", reason = \"" << reason << "\"" << LL_ENDL;
if ( gVoiceClient ) gVoiceClient->giveUp();
}
}
@@ -117,9 +161,23 @@ public:
{
if ( gVoiceClient )
{
+ std::string voice_sip_uri_hostname;
+ std::string voice_account_server_uri;
+
+ LL_DEBUGS("Voice") << "ProvisionVoiceAccountRequest response:" << ll_pretty_print_sd(content) << LL_ENDL;
+
+ if(content.has("voice_sip_uri_hostname"))
+ voice_sip_uri_hostname = content["voice_sip_uri_hostname"].asString();
+
+ // this key is actually misnamed -- it will be an entire URI, not just a hostname.
+ if(content.has("voice_account_server_name"))
+ voice_account_server_uri = content["voice_account_server_name"].asString();
+
gVoiceClient->login(
content["username"].asString(),
- content["password"].asString());
+ content["password"].asString(),
+ voice_sip_uri_hostname,
+ voice_account_server_uri);
}
}
@@ -166,21 +224,27 @@ protected:
int ignoreDepth;
// Members for processing responses. The values are transient and only valid within a call to processResponse().
+ bool squelchDebugOutput;
int returnCode;
int statusCode;
std::string statusString;
- std::string uuidString;
+ std::string requestId;
std::string actionString;
std::string connectorHandle;
+ std::string versionID;
std::string accountHandle;
std::string sessionHandle;
- std::string eventSessionHandle;
+ std::string sessionGroupHandle;
+ std::string alias;
+ std::string applicationString;
// Members for processing events. The values are transient and only valid within a call to processResponse().
std::string eventTypeString;
int state;
std::string uriString;
bool isChannel;
+ bool incoming;
+ bool enabled;
std::string nameString;
std::string audioMediaString;
std::string displayNameString;
@@ -190,6 +254,21 @@ protected:
bool isSpeaking;
int volume;
F32 energy;
+ std::string messageHeader;
+ std::string messageBody;
+ std::string notificationType;
+ bool hasText;
+ bool hasAudio;
+ bool hasVideo;
+ bool terminated;
+ std::string blockMask;
+ std::string presenceOnly;
+ std::string autoAcceptMask;
+ std::string autoAddAsBuddy;
+ int numberOfAliases;
+ std::string subscriptionHandle;
+ std::string subscriptionType;
+
// Members for processing text between tags
std::string textBuffer;
@@ -222,8 +301,6 @@ void LLVivoxProtocolParser::reset()
responseDepth = 0;
ignoringTags = false;
accumulateText = false;
- textBuffer.clear();
-
energy = 0.f;
ignoreDepth = 0;
isChannel = false;
@@ -232,10 +309,15 @@ void LLVivoxProtocolParser::reset()
isModeratorMuted = false;
isSpeaking = false;
participantType = 0;
- returnCode = 0;
+ squelchDebugOutput = false;
+ returnCode = -1;
state = 0;
statusCode = 0;
volume = 0;
+ textBuffer.clear();
+ alias.clear();
+ numberOfAliases = 0;
+ applicationString.clear();
}
//virtual
@@ -262,33 +344,11 @@ LLIOPipe::EStatus LLVivoxProtocolParser::process_impl(
mInput.append(buf, istr.gcount());
}
- // MBW -- XXX -- This should no longer be necessary. Or even possible.
- // We've read all the data out of the buffer. Make sure it doesn't accumulate.
-// buffer->clear();
-
// Look for input delimiter(s) in the input buffer. If one is found, send the message to the xml parser.
int start = 0;
int delim;
while((delim = mInput.find("\n\n\n", start)) != std::string::npos)
{
- // Turn this on to log incoming XML
- if(0)
- {
- int foo = mInput.find("Set3DPosition", start);
- int bar = mInput.find("ParticipantPropertiesEvent", start);
- if(foo != std::string::npos && (foo < delim))
- {
- // This is a Set3DPosition response. Don't print it, since these are way too spammy.
- }
- else if(bar != std::string::npos && (bar < delim))
- {
- // This is a ParticipantPropertiesEvent response. Don't print it, since these are way too spammy.
- }
- else
- {
- LL_INFOS("Voice") << "parsing: " << mInput.substr(start, delim - start) << LL_ENDL;
- }
- }
// Reset internal state of the LLVivoxProtocolParser (no effect on the expat parser)
reset();
@@ -299,13 +359,19 @@ LLIOPipe::EStatus LLVivoxProtocolParser::process_impl(
XML_SetUserData(parser, this);
XML_Parse(parser, mInput.data() + start, delim - start, false);
+ // If this message isn't set to be squelched, output the raw XML received.
+ if(!squelchDebugOutput)
+ {
+ LL_DEBUGS("Voice") << "parsing: " << mInput.substr(start, delim - start) << LL_ENDL;
+ }
+
start = delim + 3;
}
if(start != 0)
mInput = mInput.substr(start);
- LL_DEBUGS("Voice") << "at end, mInput is: " << mInput << LL_ENDL;
+ LL_DEBUGS("VivoxProtocolParser") << "at end, mInput is: " << mInput << LL_ENDL;
if(!gVoiceClient->mConnected)
{
@@ -360,9 +426,9 @@ void LLVivoxProtocolParser::StartTag(const char *tag, const char **attr)
if (responseDepth == 0)
{
- isEvent = strcmp("Event", tag) == 0;
+ isEvent = !stricmp("Event", tag);
- if (strcmp("Response", tag) == 0 || isEvent)
+ if (!stricmp("Response", tag) || isEvent)
{
// Grab the attributes
while (*attr)
@@ -370,49 +436,62 @@ void LLVivoxProtocolParser::StartTag(const char *tag, const char **attr)
const char *key = *attr++;
const char *value = *attr++;
- if (strcmp("requestId", key) == 0)
+ if (!stricmp("requestId", key))
{
- uuidString = value;
+ requestId = value;
}
- else if (strcmp("action", key) == 0)
+ else if (!stricmp("action", key))
{
actionString = value;
}
- else if (strcmp("type", key) == 0)
+ else if (!stricmp("type", key))
{
eventTypeString = value;
}
}
}
- LL_DEBUGS("Voice") << tag << " (" << responseDepth << ")" << LL_ENDL;
+ LL_DEBUGS("VivoxProtocolParser") << tag << " (" << responseDepth << ")" << LL_ENDL;
}
else
{
if (ignoringTags)
{
- LL_DEBUGS("Voice") << "ignoring tag " << tag << " (depth = " << responseDepth << ")" << LL_ENDL;
+ LL_DEBUGS("VivoxProtocolParser") << "ignoring tag " << tag << " (depth = " << responseDepth << ")" << LL_ENDL;
}
else
{
- LL_DEBUGS("Voice") << tag << " (" << responseDepth << ")" << LL_ENDL;
+ LL_DEBUGS("VivoxProtocolParser") << tag << " (" << responseDepth << ")" << LL_ENDL;
// Ignore the InputXml stuff so we don't get confused
- if (strcmp("InputXml", tag) == 0)
+ if (!stricmp("InputXml", tag))
{
ignoringTags = true;
ignoreDepth = responseDepth;
accumulateText = false;
- LL_DEBUGS("Voice") << "starting ignore, ignoreDepth is " << ignoreDepth << LL_ENDL;
+ LL_DEBUGS("VivoxProtocolParser") << "starting ignore, ignoreDepth is " << ignoreDepth << LL_ENDL;
}
- else if (strcmp("CaptureDevices", tag) == 0)
+ else if (!stricmp("CaptureDevices", tag))
{
gVoiceClient->clearCaptureDevices();
}
- else if (strcmp("RenderDevices", tag) == 0)
+ else if (!stricmp("RenderDevices", tag))
{
gVoiceClient->clearRenderDevices();
}
+ else if (!stricmp("Buddies", tag))
+ {
+ gVoiceClient->deleteAllBuddies();
+ }
+ else if (!stricmp("BlockRules", tag))
+ {
+ gVoiceClient->deleteAllBlockRules();
+ }
+ else if (!stricmp("AutoAcceptRules", tag))
+ {
+ gVoiceClient->deleteAllAutoAcceptRules();
+ }
+
}
}
responseDepth++;
@@ -431,90 +510,138 @@ void LLVivoxProtocolParser::EndTag(const char *tag)
{
if (ignoreDepth == responseDepth)
{
- LL_DEBUGS("Voice") << "end of ignore" << LL_ENDL;
+ LL_DEBUGS("VivoxProtocolParser") << "end of ignore" << LL_ENDL;
ignoringTags = false;
}
else
{
- LL_DEBUGS("Voice") << "ignoring tag " << tag << " (depth = " << responseDepth << ")" << LL_ENDL;
+ LL_DEBUGS("VivoxProtocolParser") << "ignoring tag " << tag << " (depth = " << responseDepth << ")" << LL_ENDL;
}
}
if (!ignoringTags)
{
- LL_DEBUGS("Voice") << "processing tag " << tag << " (depth = " << responseDepth << ")" << LL_ENDL;
+ LL_DEBUGS("VivoxProtocolParser") << "processing tag " << tag << " (depth = " << responseDepth << ")" << LL_ENDL;
// Closing a tag. Finalize the text we've accumulated and reset
- if (strcmp("ReturnCode", tag) == 0)
+ if (!stricmp("ReturnCode", tag))
returnCode = strtol(string.c_str(), NULL, 10);
- else if (strcmp("StatusCode", tag) == 0)
+ else if (!stricmp("SessionHandle", tag))
+ sessionHandle = string;
+ else if (!stricmp("SessionGroupHandle", tag))
+ sessionGroupHandle = string;
+ else if (!stricmp("StatusCode", tag))
statusCode = strtol(string.c_str(), NULL, 10);
- else if (strcmp("ConnectorHandle", tag) == 0)
+ else if (!stricmp("StatusString", tag))
+ statusString = string;
+ else if (!stricmp("ParticipantURI", tag))
+ uriString = string;
+ else if (!stricmp("Volume", tag))
+ volume = strtol(string.c_str(), NULL, 10);
+ else if (!stricmp("Energy", tag))
+ energy = (F32)strtod(string.c_str(), NULL);
+ else if (!stricmp("IsModeratorMuted", tag))
+ isModeratorMuted = !stricmp(string.c_str(), "true");
+ else if (!stricmp("IsSpeaking", tag))
+ isSpeaking = !stricmp(string.c_str(), "true");
+ else if (!stricmp("Alias", tag))
+ alias = string;
+ else if (!stricmp("NumberOfAliases", tag))
+ numberOfAliases = strtol(string.c_str(), NULL, 10);
+ else if (!stricmp("Application", tag))
+ applicationString = string;
+ else if (!stricmp("ConnectorHandle", tag))
connectorHandle = string;
- else if (strcmp("AccountHandle", tag) == 0)
+ else if (!stricmp("VersionID", tag))
+ versionID = string;
+ else if (!stricmp("AccountHandle", tag))
accountHandle = string;
- else if (strcmp("SessionHandle", tag) == 0)
- {
- if (isEvent)
- eventSessionHandle = string;
- else
- sessionHandle = string;
- }
- else if (strcmp("StatusString", tag) == 0)
- statusString = string;
- else if (strcmp("State", tag) == 0)
+ else if (!stricmp("State", tag))
state = strtol(string.c_str(), NULL, 10);
- else if (strcmp("URI", tag) == 0)
+ else if (!stricmp("URI", tag))
uriString = string;
- else if (strcmp("IsChannel", tag) == 0)
- isChannel = string == "true" ? true : false;
- else if (strcmp("Name", tag) == 0)
+ else if (!stricmp("IsChannel", tag))
+ isChannel = !stricmp(string.c_str(), "true");
+ else if (!stricmp("Incoming", tag))
+ incoming = !stricmp(string.c_str(), "true");
+ else if (!stricmp("Enabled", tag))
+ enabled = !stricmp(string.c_str(), "true");
+ else if (!stricmp("Name", tag))
nameString = string;
- else if (strcmp("AudioMedia", tag) == 0)
+ else if (!stricmp("AudioMedia", tag))
audioMediaString = string;
- else if (strcmp("ChannelName", tag) == 0)
+ else if (!stricmp("ChannelName", tag))
nameString = string;
- else if (strcmp("ParticipantURI", tag) == 0)
- uriString = string;
- else if (strcmp("DisplayName", tag) == 0)
+ else if (!stricmp("DisplayName", tag))
displayNameString = string;
- else if (strcmp("AccountName", tag) == 0)
+ else if (!stricmp("AccountName", tag))
nameString = string;
- else if (strcmp("ParticipantTyppe", tag) == 0)
+ else if (!stricmp("ParticipantType", tag))
participantType = strtol(string.c_str(), NULL, 10);
- else if (strcmp("IsLocallyMuted", tag) == 0)
- isLocallyMuted = string == "true" ? true : false;
- else if (strcmp("IsModeratorMuted", tag) == 0)
- isModeratorMuted = string == "true" ? true : false;
- else if (strcmp("IsSpeaking", tag) == 0)
- isSpeaking = string == "true" ? true : false;
- else if (strcmp("Volume", tag) == 0)
- volume = strtol(string.c_str(), NULL, 10);
- else if (strcmp("Energy", tag) == 0)
- energy = (F32)strtod(string.c_str(), NULL);
- else if (strcmp("MicEnergy", tag) == 0)
+ else if (!stricmp("IsLocallyMuted", tag))
+ isLocallyMuted = !stricmp(string.c_str(), "true");
+ else if (!stricmp("MicEnergy", tag))
energy = (F32)strtod(string.c_str(), NULL);
- else if (strcmp("ChannelName", tag) == 0)
+ else if (!stricmp("ChannelName", tag))
nameString = string;
- else if (strcmp("ChannelURI", tag) == 0)
+ else if (!stricmp("ChannelURI", tag))
uriString = string;
- else if (strcmp("ChannelListResult", tag) == 0)
- {
- gVoiceClient->addChannelMapEntry(nameString, uriString);
- }
- else if (strcmp("Device", tag) == 0)
+ else if (!stricmp("BuddyURI", tag))
+ uriString = string;
+ else if (!stricmp("Presence", tag))
+ statusString = string;
+ else if (!stricmp("Device", tag))
{
// This closing tag shouldn't clear the accumulated text.
clearbuffer = false;
}
- else if (strcmp("CaptureDevice", tag) == 0)
+ else if (!stricmp("CaptureDevice", tag))
{
gVoiceClient->addCaptureDevice(textBuffer);
}
- else if (strcmp("RenderDevice", tag) == 0)
+ else if (!stricmp("RenderDevice", tag))
{
gVoiceClient->addRenderDevice(textBuffer);
}
+ else if (!stricmp("Buddy", tag))
+ {
+ gVoiceClient->processBuddyListEntry(uriString, displayNameString);
+ }
+ else if (!stricmp("BlockRule", tag))
+ {
+ gVoiceClient->addBlockRule(blockMask, presenceOnly);
+ }
+ else if (!stricmp("BlockMask", tag))
+ blockMask = string;
+ else if (!stricmp("PresenceOnly", tag))
+ presenceOnly = string;
+ else if (!stricmp("AutoAcceptRule", tag))
+ {
+ gVoiceClient->addAutoAcceptRule(autoAcceptMask, autoAddAsBuddy);
+ }
+ else if (!stricmp("AutoAcceptMask", tag))
+ autoAcceptMask = string;
+ else if (!stricmp("AutoAddAsBuddy", tag))
+ autoAddAsBuddy = string;
+ else if (!stricmp("MessageHeader", tag))
+ messageHeader = string;
+ else if (!stricmp("MessageBody", tag))
+ messageBody = string;
+ else if (!stricmp("NotificationType", tag))
+ notificationType = string;
+ else if (!stricmp("HasText", tag))
+ hasText = !stricmp(string.c_str(), "true");
+ else if (!stricmp("HasAudio", tag))
+ hasAudio = !stricmp(string.c_str(), "true");
+ else if (!stricmp("HasVideo", tag))
+ hasVideo = !stricmp(string.c_str(), "true");
+ else if (!stricmp("Terminated", tag))
+ terminated = !stricmp(string.c_str(), "true");
+ else if (!stricmp("SubscriptionHandle", tag))
+ subscriptionHandle = string;
+ else if (!stricmp("SubscriptionType", tag))
+ subscriptionType = string;
+
if(clearbuffer)
{
@@ -549,144 +676,296 @@ void LLVivoxProtocolParser::CharData(const char *buffer, int length)
void LLVivoxProtocolParser::processResponse(std::string tag)
{
- LL_DEBUGS("Voice") << tag << LL_ENDL;
+ LL_DEBUGS("VivoxProtocolParser") << tag << LL_ENDL;
+ // SLIM SDK: the SDK now returns a statusCode of "200" (OK) for success. This is a change vs. previous SDKs.
+ // According to Mike S., "The actual API convention is that responses with return codes of 0 are successful, regardless of the status code returned",
+ // so I believe this will give correct behavior.
+
+ if(returnCode == 0)
+ statusCode = 0;
+
if (isEvent)
{
- if (eventTypeString == "LoginStateChangeEvent")
+ const char *eventTypeCstr = eventTypeString.c_str();
+ if (!stricmp(eventTypeCstr, "AccountLoginStateChangeEvent"))
{
- gVoiceClient->loginStateChangeEvent(accountHandle, statusCode, statusString, state);
+ gVoiceClient->accountLoginStateChangeEvent(accountHandle, statusCode, statusString, state);
}
- else if (eventTypeString == "SessionNewEvent")
+ else if (!stricmp(eventTypeCstr, "SessionAddedEvent"))
{
- gVoiceClient->sessionNewEvent(accountHandle, eventSessionHandle, state, nameString, uriString);
+ /*
+
+ c1_m1000xFnPP04IpREWNkuw1cOXlhw==_sg0
+ c1_m1000xFnPP04IpREWNkuw1cOXlhw==0
+ sip:confctl-1408789@bhr.vivox.com
+ true
+ false
+
+
+ */
+ gVoiceClient->sessionAddedEvent(uriString, alias, sessionHandle, sessionGroupHandle, isChannel, incoming, nameString, applicationString);
}
- else if (eventTypeString == "SessionStateChangeEvent")
+ else if (!stricmp(eventTypeCstr, "SessionRemovedEvent"))
{
- gVoiceClient->sessionStateChangeEvent(uriString, statusCode, statusString, eventSessionHandle, state, isChannel, nameString);
+ gVoiceClient->sessionRemovedEvent(sessionHandle, sessionGroupHandle);
}
- else if (eventTypeString == "ParticipantStateChangeEvent")
+ else if (!stricmp(eventTypeCstr, "SessionGroupAddedEvent"))
{
- gVoiceClient->participantStateChangeEvent(uriString, statusCode, statusString, state, nameString, displayNameString, participantType);
-
+ gVoiceClient->sessionGroupAddedEvent(sessionGroupHandle);
+ }
+ else if (!stricmp(eventTypeCstr, "MediaStreamUpdatedEvent"))
+ {
+ /*
+
+ c1_m1000xFnPP04IpREWNkuw1cOXlhw==_sg0
+ c1_m1000xFnPP04IpREWNkuw1cOXlhw==0
+ 200
+ OK
+ 2
+ false
+
+ */
+ gVoiceClient->mediaStreamUpdatedEvent(sessionHandle, sessionGroupHandle, statusCode, statusString, state, incoming);
+ }
+ else if (!stricmp(eventTypeCstr, "TextStreamUpdatedEvent"))
+ {
+ /*
+
+ c1_m1000xFnPP04IpREWNkuw1cOXlhw==_sg1
+ c1_m1000xFnPP04IpREWNkuw1cOXlhw==1
+ true
+ 1
+ true
+
+ */
+ gVoiceClient->textStreamUpdatedEvent(sessionHandle, sessionGroupHandle, enabled, state, incoming);
+ }
+ else if (!stricmp(eventTypeCstr, "ParticipantAddedEvent"))
+ {
+ /*
+
+ c1_m1000xFnPP04IpREWNkuw1cOXlhw==_sg4
+ c1_m1000xFnPP04IpREWNkuw1cOXlhw==4
+ sip:xI5auBZ60SJWIk606-1JGRQ==@bhr.vivox.com
+ xI5auBZ60SJWIk606-1JGRQ==
+
+ 0
+
+ */
+ gVoiceClient->participantAddedEvent(sessionHandle, sessionGroupHandle, uriString, alias, nameString, displayNameString, participantType);
+ }
+ else if (!stricmp(eventTypeCstr, "ParticipantRemovedEvent"))
+ {
+ /*
+
+ c1_m1000xFnPP04IpREWNkuw1cOXlhw==_sg4
+ c1_m1000xFnPP04IpREWNkuw1cOXlhw==4
+ sip:xtx7YNV-3SGiG7rA1fo5Ndw==@bhr.vivox.com
+ xtx7YNV-3SGiG7rA1fo5Ndw==
+
+ */
+ gVoiceClient->participantRemovedEvent(sessionHandle, sessionGroupHandle, uriString, alias, nameString);
}
- else if (eventTypeString == "ParticipantPropertiesEvent")
+ else if (!stricmp(eventTypeCstr, "ParticipantUpdatedEvent"))
{
- gVoiceClient->participantPropertiesEvent(uriString, statusCode, statusString, isLocallyMuted, isModeratorMuted, isSpeaking, volume, energy);
+ /*
+
+ c1_m1000xFnPP04IpREWNkuw1cOXlhw==_sg0
+ c1_m1000xFnPP04IpREWNkuw1cOXlhw==0
+ sip:xFnPP04IpREWNkuw1cOXlhw==@bhr.vivox.com
+ false
+ true
+ 44
+ 0.0879437
+
+ */
+
+ // These happen so often that logging them is pretty useless.
+ squelchDebugOutput = true;
+
+ gVoiceClient->participantUpdatedEvent(sessionHandle, sessionGroupHandle, uriString, alias, isModeratorMuted, isSpeaking, volume, energy);
}
- else if (eventTypeString == "AuxAudioPropertiesEvent")
+ else if (!stricmp(eventTypeCstr, "AuxAudioPropertiesEvent"))
{
gVoiceClient->auxAudioPropertiesEvent(energy);
}
+ else if (!stricmp(eventTypeCstr, "BuddyPresenceEvent"))
+ {
+ gVoiceClient->buddyPresenceEvent(uriString, alias, statusString, applicationString);
+ }
+ else if (!stricmp(eventTypeCstr, "BuddyAndGroupListChangedEvent"))
+ {
+ // The buddy list was updated during parsing.
+ // Need to recheck against the friends list.
+ gVoiceClient->buddyListChanged();
+ }
+ else if (!stricmp(eventTypeCstr, "BuddyChangedEvent"))
+ {
+ /*
+
+ c1_m1000xFnPP04IpREWNkuw1cOXlhw==
+ sip:x9fFHFZjOTN6OESF1DUPrZQ==@bhr.vivox.com
+ Monroe Tester
+
+ 0
+ Set
+
+ */
+ // TODO: Question: Do we need to process this at all?
+ }
+ else if (!stricmp(eventTypeCstr, "MessageEvent"))
+ {
+ gVoiceClient->messageEvent(sessionHandle, uriString, alias, messageHeader, messageBody, applicationString);
+ }
+ else if (!stricmp(eventTypeCstr, "SessionNotificationEvent"))
+ {
+ gVoiceClient->sessionNotificationEvent(sessionHandle, uriString, notificationType);
+ }
+ else if (!stricmp(eventTypeCstr, "SubscriptionEvent"))
+ {
+ gVoiceClient->subscriptionEvent(uriString, subscriptionHandle, alias, displayNameString, applicationString, subscriptionType);
+ }
+ else if (!stricmp(eventTypeCstr, "SessionUpdatedEvent"))
+ {
+ /*
+
+ c1_m1000xFnPP04IpREWNkuw1cOXlhw==_sg0
+ c1_m1000xFnPP04IpREWNkuw1cOXlhw==0
+ sip:confctl-9@bhd.vivox.com
+ 0
+ 50
+ 1
+ 0
+ 000
+ 0
+
+ */
+ // We don't need to process this, but we also shouldn't warn on it, since that confuses people.
+ }
+ else
+ {
+ LL_WARNS("VivoxProtocolParser") << "Unknown event type " << eventTypeString << LL_ENDL;
+ }
}
else
{
- if (actionString == "Connector.Create.1")
+ const char *actionCstr = actionString.c_str();
+ if (!stricmp(actionCstr, "Connector.Create.1"))
{
- gVoiceClient->connectorCreateResponse(statusCode, statusString, connectorHandle);
+ gVoiceClient->connectorCreateResponse(statusCode, statusString, connectorHandle, versionID);
}
- else if (actionString == "Account.Login.1")
+ else if (!stricmp(actionCstr, "Account.Login.1"))
{
- gVoiceClient->loginResponse(statusCode, statusString, accountHandle);
+ gVoiceClient->loginResponse(statusCode, statusString, accountHandle, numberOfAliases);
}
- else if (actionString == "Session.Create.1")
+ else if (!stricmp(actionCstr, "Session.Create.1"))
{
- gVoiceClient->sessionCreateResponse(statusCode, statusString, sessionHandle);
+ gVoiceClient->sessionCreateResponse(requestId, statusCode, statusString, sessionHandle);
}
- else if (actionString == "Session.Connect.1")
+ else if (!stricmp(actionCstr, "SessionGroup.AddSession.1"))
{
- gVoiceClient->sessionConnectResponse(statusCode, statusString);
+ gVoiceClient->sessionGroupAddSessionResponse(requestId, statusCode, statusString, sessionHandle);
}
- else if (actionString == "Session.Terminate.1")
+ else if (!stricmp(actionCstr, "Session.Connect.1"))
{
- gVoiceClient->sessionTerminateResponse(statusCode, statusString);
+ gVoiceClient->sessionConnectResponse(requestId, statusCode, statusString);
}
- else if (actionString == "Account.Logout.1")
+ else if (!stricmp(actionCstr, "Account.Logout.1"))
{
gVoiceClient->logoutResponse(statusCode, statusString);
}
- else if (actionString == "Connector.InitiateShutdown.1")
+ else if (!stricmp(actionCstr, "Connector.InitiateShutdown.1"))
{
gVoiceClient->connectorShutdownResponse(statusCode, statusString);
}
- else if (actionString == "Account.ChannelGetList.1")
+ else if (!stricmp(actionCstr, "Account.ListBlockRules.1"))
{
- gVoiceClient->channelGetListResponse(statusCode, statusString);
+ gVoiceClient->accountListBlockRulesResponse(statusCode, statusString);
+ }
+ else if (!stricmp(actionCstr, "Account.ListAutoAcceptRules.1"))
+ {
+ gVoiceClient->accountListAutoAcceptRulesResponse(statusCode, statusString);
+ }
+ else if (!stricmp(actionCstr, "Session.Set3DPosition.1"))
+ {
+ // We don't need to process these, but they're so spammy we don't want to log them.
+ squelchDebugOutput = true;
}
/*
- else if (actionString == "Connector.AccountCreate.1")
+ else if (!stricmp(actionCstr, "Account.ChannelGetList.1"))
{
-
+ gVoiceClient->channelGetListResponse(statusCode, statusString);
}
- else if (actionString == "Connector.MuteLocalMic.1")
+ else if (!stricmp(actionCstr, "Connector.AccountCreate.1"))
{
}
- else if (actionString == "Connector.MuteLocalSpeaker.1")
+ else if (!stricmp(actionCstr, "Connector.MuteLocalMic.1"))
{
}
- else if (actionString == "Connector.SetLocalMicVolume.1")
+ else if (!stricmp(actionCstr, "Connector.MuteLocalSpeaker.1"))
{
}
- else if (actionString == "Connector.SetLocalSpeakerVolume.1")
+ else if (!stricmp(actionCstr, "Connector.SetLocalMicVolume.1"))
{
}
- else if (actionString == "Session.ListenerSetPosition.1")
+ else if (!stricmp(actionCstr, "Connector.SetLocalSpeakerVolume.1"))
{
}
- else if (actionString == "Session.SpeakerSetPosition.1")
+ else if (!stricmp(actionCstr, "Session.ListenerSetPosition.1"))
{
}
- else if (actionString == "Session.Set3DPosition.1")
+ else if (!stricmp(actionCstr, "Session.SpeakerSetPosition.1"))
{
}
- else if (actionString == "Session.AudioSourceSetPosition.1")
+ else if (!stricmp(actionCstr, "Session.AudioSourceSetPosition.1"))
{
}
- else if (actionString == "Session.GetChannelParticipants.1")
+ else if (!stricmp(actionCstr, "Session.GetChannelParticipants.1"))
{
}
- else if (actionString == "Account.ChannelCreate.1")
+ else if (!stricmp(actionCstr, "Account.ChannelCreate.1"))
{
}
- else if (actionString == "Account.ChannelUpdate.1")
+ else if (!stricmp(actionCstr, "Account.ChannelUpdate.1"))
{
}
- else if (actionString == "Account.ChannelDelete.1")
+ else if (!stricmp(actionCstr, "Account.ChannelDelete.1"))
{
}
- else if (actionString == "Account.ChannelCreateAndInvite.1")
+ else if (!stricmp(actionCstr, "Account.ChannelCreateAndInvite.1"))
{
}
- else if (actionString == "Account.ChannelFolderCreate.1")
+ else if (!stricmp(actionCstr, "Account.ChannelFolderCreate.1"))
{
}
- else if (actionString == "Account.ChannelFolderUpdate.1")
+ else if (!stricmp(actionCstr, "Account.ChannelFolderUpdate.1"))
{
}
- else if (actionString == "Account.ChannelFolderDelete.1")
+ else if (!stricmp(actionCstr, "Account.ChannelFolderDelete.1"))
{
}
- else if (actionString == "Account.ChannelAddModerator.1")
+ else if (!stricmp(actionCstr, "Account.ChannelAddModerator.1"))
{
}
- else if (actionString == "Account.ChannelDeleteModerator.1")
+ else if (!stricmp(actionCstr, "Account.ChannelDeleteModerator.1"))
{
}
@@ -700,9 +979,18 @@ class LLVoiceClientMuteListObserver : public LLMuteListObserver
{
/* virtual */ void onChange() { gVoiceClient->muteListChanged();}
};
+
+class LLVoiceClientFriendsObserver : public LLFriendObserver
+{
+public:
+ /* virtual */ void changed(U32 mask) { gVoiceClient->updateFriends(mask);}
+};
+
static LLVoiceClientMuteListObserver mutelist_listener;
static bool sMuteListListener_listening = false;
+static LLVoiceClientFriendsObserver *friendslist_listener = NULL;
+
///////////////////////////////////////////////////////////////////////////////////////////////
class LLVoiceClientCapResponder : public LLHTTPClient::Responder
@@ -726,11 +1014,8 @@ void LLVoiceClientCapResponder::error(U32 status, const std::string& reason)
void LLVoiceClientCapResponder::result(const LLSD& content)
{
LLSD::map_const_iterator iter;
- for(iter = content.beginMap(); iter != content.endMap(); ++iter)
- {
- LL_DEBUGS("Voice") << "LLVoiceClientCapResponder::result got "
- << iter->first << LL_ENDL;
- }
+
+ LL_DEBUGS("Voice") << "ParcelVoiceInfoRequest response:" << ll_pretty_print_sd(content) << LL_ENDL;
if ( content.has("voice_credentials") )
{
@@ -816,26 +1101,26 @@ LLVoiceClient::LLVoiceClient()
mUserPTTState = false;
mMuteMic = false;
mSessionTerminateRequested = false;
+ mRelogRequested = false;
mCommandCookie = 0;
- mNonSpatialChannel = false;
- mNextSessionSpatial = true;
- mNextSessionNoReconnect = false;
- mSessionP2P = false;
mCurrentParcelLocalID = 0;
mLoginRetryCount = 0;
- mVivoxErrorStatusCode = 0;
- mNextSessionResetOnClose = false;
- mSessionResetOnClose = false;
mSpeakerVolume = 0;
mMicVolume = 0;
+ mAudioSession = NULL;
+ mAudioSessionChanged = false;
+
// Initial dirty state
mSpatialCoordsDirty = false;
mPTTDirty = true;
- mVolumeDirty = true;
+ mFriendsListDirty = true;
mSpeakerVolumeDirty = true;
mMicVolumeDirty = true;
+ mBuddyListMapPopulated = false;
+ mBlockRulesListReceived = false;
+ mAutoAcceptRulesListReceived = false;
mCaptureDeviceDirty = false;
mRenderDeviceDirty = false;
@@ -856,14 +1141,12 @@ LLVoiceClient::LLVoiceClient()
// gMuteListp isn't set up at this point, so we defer this until later.
// gMuteListp->addObserver(&mutelist_listener);
- mParticipantMapChanged = false;
-
// stash the pump for later use
// This now happens when init() is called instead.
mPump = NULL;
#if LL_DARWIN || LL_LINUX
- // MBW -- XXX -- THIS DOES NOT BELONG HERE
+ // HACK: THIS DOES NOT BELONG HERE
// When the vivox daemon dies, the next write attempt on our socket generates a SIGPIPE, which kills us.
// This should cause us to ignore SIGPIPE and handle the error through proper channels.
// This should really be set up elsewhere. Where should it go?
@@ -899,8 +1182,10 @@ void LLVoiceClient::terminate()
{
if(gVoiceClient)
{
- gVoiceClient->sessionTerminateSendMessage();
+// gVoiceClient->leaveAudioSession();
gVoiceClient->logout();
+ // As of SDK version 4885, this should no longer be necessary. It will linger after the socket close if it needs to.
+ // ms_sleep(2000);
gVoiceClient->connectorShutdown();
gVoiceClient->closeSocket(); // Need to do this now -- bad things happen if the destructor does it later.
@@ -925,8 +1210,6 @@ void LLVoiceClient::updateSettings()
setPTTKey(keyString);
setPTTIsToggle(gSavedSettings.getBOOL("PushToTalkToggle"));
setEarLocation(gSavedSettings.getS32("VoiceEarLocation"));
- std::string serverName = gSavedSettings.getString("VivoxDebugServerName");
- setVivoxDebugServerName(serverName);
std::string inputDevice = gSavedSettings.getString("VoiceInputAudioDevice");
setCaptureDevice(inputDevice);
@@ -949,9 +1232,10 @@ bool LLVoiceClient::writeString(const std::string &str)
apr_size_t size = (apr_size_t)str.size();
apr_size_t written = size;
- LL_DEBUGS("Voice") << "sending: " << str << LL_ENDL;
+ //MARK: Turn this on to log outgoing XML
+// LL_DEBUGS("Voice") << "sending: " << str << LL_ENDL;
- // MBW -- XXX -- check return code - sockets will fail (broken, etc.)
+ // check return code - sockets will fail (broken, etc.)
err = apr_socket_send(
mSocket->getSocket(),
(const char*)str.data(),
@@ -962,7 +1246,7 @@ bool LLVoiceClient::writeString(const std::string &str)
// Success.
result = true;
}
- // MBW -- XXX -- handle partial writes (written is number of bytes written)
+ // TODO: handle partial writes (written is number of bytes written)
// Need to set socket to non-blocking before this will work.
// else if(APR_STATUS_IS_EAGAIN(err))
// {
@@ -986,7 +1270,7 @@ bool LLVoiceClient::writeString(const std::string &str)
void LLVoiceClient::connectorCreate()
{
std::ostringstream stream;
- std::string logpath;
+ std::string logpath = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "");
std::string loglevel = "0";
// Transition to stateConnectorStarted when the connector handle comes back.
@@ -998,20 +1282,20 @@ void LLVoiceClient::connectorCreate()
{
LL_DEBUGS("Voice") << "creating connector with logging enabled" << LL_ENDL;
loglevel = "10";
- logpath = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "");
}
stream
<< ""
<< "V2 SDK"
- << "" << mAccountServerURI << ""
+ << "" << mVoiceAccountServerURI << ""
+ << "Normal"
<< ""
- << "false"
<< "" << logpath << ""
<< "Connector"
<< ".log"
<< "" << loglevel << ""
<< ""
+ << "SecondLifeViewer.1"
<< "\n\n\n";
writeString(stream.str());
@@ -1049,20 +1333,6 @@ void LLVoiceClient::userAuthorized(const std::string& firstName, const std::stri
sConnectingToAgni = LLViewerLogin::getInstance()->isInProductionGrid();
- // MBW -- XXX -- Enable this when the bhd.vivox.com server gets a real ssl cert.
- if(sConnectingToAgni)
- {
- // Use the release account server
- mAccountServerName = "bhr.vivox.com";
- mAccountServerURI = "https://www." + mAccountServerName + "/api2/";
- }
- else
- {
- // Use the development account server
- mAccountServerName = gSavedSettings.getString("VivoxDebugServerName");
- mAccountServerURI = "https://www." + mAccountServerName + "/api2/";
- }
-
mAccountName = nameFromID(agentID);
}
@@ -1084,24 +1354,66 @@ void LLVoiceClient::requestVoiceAccountProvision(S32 retries)
}
void LLVoiceClient::login(
- const std::string& accountName,
- const std::string &password)
+ const std::string& account_name,
+ const std::string& password,
+ const std::string& voice_sip_uri_hostname,
+ const std::string& voice_account_server_uri)
{
+ mVoiceSIPURIHostName = voice_sip_uri_hostname;
+ mVoiceAccountServerURI = voice_account_server_uri;
+
if((getState() >= stateLoggingIn) && (getState() < stateLoggedOut))
{
// Already logged in. This is an internal error.
LL_ERRS("Voice") << "Can't login again. Called from wrong state." << LL_ENDL;
}
- else if ( accountName != mAccountName )
+ else if ( account_name != mAccountName )
{
//TODO: error?
- LL_WARNS("Voice") << "Wrong account name! " << accountName
+ LL_WARNS("Voice") << "Wrong account name! " << account_name
<< " instead of " << mAccountName << LL_ENDL;
}
else
{
mAccountPassword = password;
}
+
+ std::string debugSIPURIHostName = gSavedSettings.getString("VivoxDebugSIPURIHostName");
+
+ if( !debugSIPURIHostName.empty() )
+ {
+ mVoiceSIPURIHostName = debugSIPURIHostName;
+ }
+
+ if( mVoiceSIPURIHostName.empty() )
+ {
+ // we have an empty account server name
+ // so we fall back to hardcoded defaults
+
+ if(sConnectingToAgni)
+ {
+ // Use the release account server
+ mVoiceSIPURIHostName = "bhr.vivox.com";
+ }
+ else
+ {
+ // Use the development account server
+ mVoiceSIPURIHostName = "bhd.vivox.com";
+ }
+ }
+
+ std::string debugAccountServerURI = gSavedSettings.getString("VivoxDebugVoiceAccountServerURI");
+
+ if( !debugAccountServerURI.empty() )
+ {
+ mVoiceAccountServerURI = debugAccountServerURI;
+ }
+
+ if( mVoiceAccountServerURI.empty() )
+ {
+ // If the account server URI isn't specified, construct it from the SIP URI hostname
+ mVoiceAccountServerURI = "https://www." + mVoiceSIPURIHostName + "/api2/";
+ }
}
void LLVoiceClient::idle(void* user_data)
@@ -1124,6 +1436,7 @@ std::string LLVoiceClient::state2string(LLVoiceClient::state inState)
CASE(stateDaemonLaunched);
CASE(stateConnecting);
CASE(stateIdle);
+ CASE(stateNeedsProvision);
CASE(stateConnectorStart);
CASE(stateConnectorStarting);
CASE(stateConnectorStarted);
@@ -1132,12 +1445,11 @@ std::string LLVoiceClient::state2string(LLVoiceClient::state inState)
CASE(stateNeedsLogin);
CASE(stateLoggingIn);
CASE(stateLoggedIn);
+ CASE(stateCreatingSessionGroup);
CASE(stateNoChannel);
CASE(stateMicTuningStart);
CASE(stateMicTuningRunning);
CASE(stateMicTuningStop);
- CASE(stateSessionCreate);
- CASE(stateSessionConnect);
CASE(stateJoiningSession);
CASE(stateSessionJoined);
CASE(stateRunning);
@@ -1176,6 +1488,7 @@ std::string LLVoiceClientStatusObserver::status2string(LLVoiceClientStatusObserv
CASE(STATUS_JOINING);
CASE(STATUS_JOINED);
CASE(STATUS_LEFT_CHANNEL);
+ CASE(STATUS_VOICE_DISABLED);
CASE(BEGIN_ERROR_STATUS);
CASE(ERROR_CHANNEL_FULL);
CASE(ERROR_CHANNEL_LOCKED);
@@ -1221,12 +1534,15 @@ void LLVoiceClient::stateMachine()
killGateway();
}
- sessionTerminateSendMessage();
+// leaveAudioSession();
logout();
+ // As of SDK version 4885, this should no longer be necessary. It will linger after the socket close if it needs to.
+ // ms_sleep(2000);
connectorShutdown();
closeSocket();
- removeAllParticipants();
-
+ deleteAllSessions();
+ deleteAllBuddies();
+
setState(stateDisabled);
}
}
@@ -1242,7 +1558,7 @@ void LLVoiceClient::stateMachine()
std::string regionName = region->getName();
std::string capURI = region->getCapability("ParcelVoiceInfoRequest");
- LL_DEBUGS("Voice") << "Region name = \"" << regionName <<"\", " << "parcel local ID = " << parcelLocalID << LL_ENDL;
+// LL_DEBUGS("Voice") << "Region name = \"" << regionName <<"\", " << "parcel local ID = " << parcelLocalID << LL_ENDL;
// The region name starts out empty and gets filled in later.
// Also, the cap gets filled in a short time after the region cross, but a little too late for our purposes.
@@ -1263,6 +1579,7 @@ void LLVoiceClient::stateMachine()
switch(getState())
{
+ //MARK: stateDisabled
case stateDisabled:
if(mVoiceEnabled && (!mAccountName.empty() || mTuningMode))
{
@@ -1270,6 +1587,7 @@ void LLVoiceClient::stateMachine()
}
break;
+ //MARK: stateStart
case stateStart:
if(gSavedSettings.getBOOL("CmdLineDisableVoice"))
{
@@ -1300,7 +1618,9 @@ void LLVoiceClient::stateMachine()
if(!LLFile::stat(exe_path, &s))
{
// vivox executable exists. Build the command line and launch the daemon.
- std::string args = " -p tcp -h -c";
+ // SLIM SDK: these arguments are no longer necessary.
+// std::string args = " -p tcp -h -c";
+ std::string args;
std::string cmd;
std::string loglevel = gSavedSettings.getString("VivoxDebugLevel");
@@ -1385,14 +1705,15 @@ void LLVoiceClient::stateMachine()
}
else
{
- LL_INFOS("Voice") << exe_path << "not found." << LL_ENDL;
+ LL_INFOS("Voice") << exe_path << " not found." << LL_ENDL;
}
}
else
{
+ // SLIM SDK: port changed from 44124 to 44125.
// We can connect to a client gateway running on another host. This is useful for testing.
// To do this, launch the gateway on a nearby host like this:
- // vivox-gw.exe -p tcp -i 0.0.0.0:44124
+ // vivox-gw.exe -p tcp -i 0.0.0.0:44125
// and put that host's IP address here.
mDaemonHost = LLHost(gSavedSettings.getString("VoiceHost"), gSavedSettings.getU32("VoicePort"));
}
@@ -1404,17 +1725,23 @@ void LLVoiceClient::stateMachine()
// Dirty the states we'll need to sync with the daemon when it comes up.
mPTTDirty = true;
+ mMicVolumeDirty = true;
mSpeakerVolumeDirty = true;
+ mSpeakerMuteDirty = true;
// These only need to be set if they're not default (i.e. empty string).
mCaptureDeviceDirty = !mCaptureDevice.empty();
mRenderDeviceDirty = !mRenderDevice.empty();
+
+ mMainSessionGroupHandle.clear();
}
break;
-
+
+ //MARK: stateDaemonLaunched
case stateDaemonLaunched:
- LL_DEBUGS("Voice") << "Connecting to vivox daemon" << LL_ENDL;
if(mUpdateTimer.hasExpired())
{
+ LL_DEBUGS("Voice") << "Connecting to vivox daemon" << LL_ENDL;
+
mUpdateTimer.setTimerExpirySec(CONNECT_THROTTLE_SECONDS);
if(!mSocket)
@@ -1435,6 +1762,7 @@ void LLVoiceClient::stateMachine()
}
break;
+ //MARK: stateConnecting
case stateConnecting:
// Can't do this until we have the pump available.
if(mPump)
@@ -1457,6 +1785,7 @@ void LLVoiceClient::stateMachine()
break;
+ //MARK: stateIdle
case stateIdle:
// Initial devices query
getCaptureDevicesSendMessage();
@@ -1464,17 +1793,48 @@ void LLVoiceClient::stateMachine()
mLoginRetryCount = 0;
- setState(stateConnectorStart);
+ setState(stateNeedsProvision);
+
+ break;
+
+ //MARK: stateNeedsProvision
+ case stateNeedsProvision:
+ if(!mVoiceEnabled)
+ {
+ // We were never logged in. This will shut down the connector.
+ setState(stateLoggedOut);
+ }
+ else if(!mAccountName.empty())
+ {
+ LLViewerRegion *region = gAgent.getRegion();
+ if(region)
+ {
+ if ( region->getCapability("ProvisionVoiceAccountRequest") != "" )
+ {
+ if ( mAccountPassword.empty() )
+ {
+ requestVoiceAccountProvision();
+ }
+ setState(stateConnectorStart);
+ }
+ }
+ }
+ else if(mTuningMode)
+ {
+ mTuningExitState = stateNeedsProvision;
+ setState(stateMicTuningStart);
+ }
break;
+ //MARK: stateConnectorStart
case stateConnectorStart:
if(!mVoiceEnabled)
{
// We were never logged in. This will shut down the connector.
setState(stateLoggedOut);
}
- else if(!mAccountServerURI.empty())
+ else if(!mVoiceAccountServerURI.empty())
{
connectorCreate();
}
@@ -1485,34 +1845,26 @@ void LLVoiceClient::stateMachine()
}
break;
+ //MARK: stateConnectorStarting
case stateConnectorStarting: // waiting for connector handle
// connectorCreateResponse() will transition from here to stateConnectorStarted.
break;
+ //MARK: stateConnectorStarted
case stateConnectorStarted: // connector handle received
if(!mVoiceEnabled)
{
// We were never logged in. This will shut down the connector.
setState(stateLoggedOut);
}
- else if(!mAccountName.empty())
+ else
{
- LLViewerRegion *region = gAgent.getRegion();
-
- if(region)
- {
- if ( region->getCapability("ProvisionVoiceAccountRequest") != "" )
- {
- if ( mAccountPassword.empty() )
- {
- requestVoiceAccountProvision();
- }
- setState(stateNeedsLogin);
- }
- }
+ // The connector is started. Send a login message.
+ setState(stateNeedsLogin);
}
break;
+ //MARK: stateMicTuningStart
case stateMicTuningStart:
if(mUpdateTimer.hasExpired())
{
@@ -1520,19 +1872,9 @@ void LLVoiceClient::stateMachine()
{
// These can't be changed while in tuning mode. Set them before starting.
std::ostringstream stream;
-
- if(mCaptureDeviceDirty)
- {
- buildSetCaptureDevice(stream);
- }
-
- if(mRenderDeviceDirty)
- {
- buildSetRenderDevice(stream);
- }
-
- mCaptureDeviceDirty = false;
- mRenderDeviceDirty = false;
+
+ buildSetCaptureDevice(stream);
+ buildSetRenderDevice(stream);
if(!stream.str().empty())
{
@@ -1554,6 +1896,7 @@ void LLVoiceClient::stateMachine()
break;
+ //MARK: stateMicTuningRunning
case stateMicTuningRunning:
if(!mTuningMode || !mVoiceEnabled || mSessionTerminateRequested || mCaptureDeviceDirty || mRenderDeviceDirty)
{
@@ -1595,6 +1938,7 @@ void LLVoiceClient::stateMachine()
}
break;
+ //MARK: stateMicTuningStop
case stateMicTuningStop:
{
// transition out of mic tuning
@@ -1609,6 +1953,7 @@ void LLVoiceClient::stateMachine()
}
break;
+ //MARK: stateLoginRetry
case stateLoginRetry:
if(mLoginRetryCount == 0)
{
@@ -1632,6 +1977,7 @@ void LLVoiceClient::stateMachine()
}
break;
+ //MARK: stateLoginRetryWait
case stateLoginRetryWait:
if(mUpdateTimer.hasExpired())
{
@@ -1639,6 +1985,7 @@ void LLVoiceClient::stateMachine()
}
break;
+ //MARK: stateNeedsLogin
case stateNeedsLogin:
if(!mAccountPassword.empty())
{
@@ -1647,16 +1994,22 @@ void LLVoiceClient::stateMachine()
}
break;
+ //MARK: stateLoggingIn
case stateLoggingIn: // waiting for account handle
// loginResponse() will transition from here to stateLoggedIn.
break;
+ //MARK: stateLoggedIn
case stateLoggedIn: // account handle received
- // Initial kick-off of channel lookup logic
- parcelChanged();
notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_LOGGED_IN);
+ // request the current set of block rules (we'll need them when updating the friends list)
+ accountListBlockRulesSendMessage();
+
+ // request the current set of auto-accept rules
+ accountListAutoAcceptRulesSendMessage();
+
// Set up the mute list observer if it hasn't been set up already.
if((!sMuteListListener_listening))
{
@@ -1664,13 +2017,67 @@ void LLVoiceClient::stateMachine()
sMuteListListener_listening = true;
}
+ // Set up the friends list observer if it hasn't been set up already.
+ if(friendslist_listener == NULL)
+ {
+ friendslist_listener = new LLVoiceClientFriendsObserver;
+ LLAvatarTracker::instance().addObserver(friendslist_listener);
+ }
+
+ // Set the initial state of mic mute, local speaker volume, etc.
+ {
+ std::ostringstream stream;
+
+ buildLocalAudioUpdates(stream);
+
+ if(!stream.str().empty())
+ {
+ writeString(stream.str());
+ }
+ }
+
+#if USE_SESSION_GROUPS
+ // create the main session group
+ sessionGroupCreateSendMessage();
+
+ setState(stateCreatingSessionGroup);
+#else
+ // Not using session groups -- skip the stateCreatingSessionGroup state.
setState(stateNoChannel);
+
+ // Initial kick-off of channel lookup logic
+ parcelChanged();
+#endif
+ break;
+
+ //MARK: stateCreatingSessionGroup
+ case stateCreatingSessionGroup:
+ if(mSessionTerminateRequested || !mVoiceEnabled)
+ {
+ // TODO: Question: is this the right way out of this state
+ setState(stateSessionTerminated);
+ }
+ else if(!mMainSessionGroupHandle.empty())
+ {
+ setState(stateNoChannel);
+
+ // Start looped recording (needed for "panic button" anti-griefing tool)
+ recordingLoopStart();
+
+ // Initial kick-off of channel lookup logic
+ parcelChanged();
+ }
break;
+ //MARK: stateNoChannel
case stateNoChannel:
+ // Do this here as well as inside sendPositionalUpdate().
+ // Otherwise, if you log in but don't join a proximal channel (such as when your login location has voice disabled), your friends list won't sync.
+ sendFriendsListUpdates();
+
if(mSessionTerminateRequested || !mVoiceEnabled)
{
- // MBW -- XXX -- Is this the right way out of this state?
+ // TODO: Question: Is this the right way out of this state?
setState(stateSessionTerminated);
}
else if(mTuningMode)
@@ -1678,30 +2085,49 @@ void LLVoiceClient::stateMachine()
mTuningExitState = stateNoChannel;
setState(stateMicTuningStart);
}
- else if(!mNextSessionHandle.empty())
+ else if(sessionNeedsRelog(mNextAudioSession))
{
- setState(stateSessionConnect);
+ requestRelog();
+ setState(stateSessionTerminated);
+ }
+ else if(mNextAudioSession)
+ {
+ sessionState *oldSession = mAudioSession;
+
+ mAudioSession = mNextAudioSession;
+ if(!mAudioSession->mReconnect)
+ {
+ mNextAudioSession = NULL;
+ }
+
+ // The old session may now need to be deleted.
+ reapSession(oldSession);
+
+ if(!mAudioSession->mHandle.empty())
+ {
+ // Connect to a session by session handle
+
+ sessionMediaConnectSendMessage(mAudioSession);
+ }
+ else
+ {
+ // Connect to a session by URI
+ sessionCreateSendMessage(mAudioSession, true, false);
+ }
+
+ notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_JOINING);
+ setState(stateJoiningSession);
}
- else if(!mNextSessionURI.empty())
+ else if(!mSpatialSessionURI.empty())
{
- setState(stateSessionCreate);
+ // If we're not headed elsewhere and have a spatial URI, return to spatial.
+ switchChannel(mSpatialSessionURI, true, false, false, mSpatialSessionCredentials);
}
break;
- case stateSessionCreate:
- sessionCreateSendMessage();
- notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_JOINING);
- setState(stateJoiningSession);
- break;
-
- case stateSessionConnect:
- sessionConnectSendMessage();
- notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_JOINING);
- setState(stateJoiningSession);
- break;
-
+ //MARK: stateJoiningSession
case stateJoiningSession: // waiting for session handle
- // sessionCreateResponse() will transition from here to stateSessionJoined.
+ // joinedAudioSession() will transition from here to stateSessionJoined.
if(!mVoiceEnabled)
{
// User bailed out during connect -- jump straight to teardown.
@@ -1709,30 +2135,27 @@ void LLVoiceClient::stateMachine()
}
else if(mSessionTerminateRequested)
{
- if(!mSessionHandle.empty())
+ if(mAudioSession && !mAudioSession->mHandle.empty())
{
// Only allow direct exits from this state in p2p calls (for cancelling an invite).
// Terminating a half-connected session on other types of calls seems to break something in the vivox gateway.
- if(mSessionP2P)
+ if(mAudioSession->mIsP2P)
{
- sessionTerminateSendMessage();
+ sessionMediaDisconnectSendMessage(mAudioSession);
setState(stateSessionTerminated);
}
}
}
break;
+ //MARK: stateSessionJoined
case stateSessionJoined: // session handle received
- // MBW -- XXX -- It appears that I need to wait for BOTH the Session.Create response and the SessionStateChangeEvent with state 4
- // before continuing from this state. They can happen in either order, and if I don't wait for both, things can get stuck.
- // For now, the Session.Create response handler sets mSessionHandle and the SessionStateChangeEvent handler transitions to stateSessionJoined.
+ // It appears that I need to wait for BOTH the SessionGroup.AddSession response and the SessionStateChangeEvent with state 4
+ // before continuing from this state. They can happen in either order, and if I don't wait for both, things can get stuck.
+ // For now, the SessionGroup.AddSession response handler sets mSessionHandle and the SessionStateChangeEvent handler transitions to stateSessionJoined.
// This is a cheap way to make sure both have happened before proceeding.
- if(!mSessionHandle.empty())
+ if(mAudioSession && mAudioSession->mVoiceEnabled)
{
- // Events that need to happen when a session is joined could go here.
- // Maybe send initial spatial data?
- notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_JOINED);
-
// Dirty state that may need to be sync'ed with the daemon.
mPTTDirty = true;
mSpeakerVolumeDirty = true;
@@ -1743,8 +2166,13 @@ void LLVoiceClient::stateMachine()
// Start the throttle timer
mUpdateTimer.start();
mUpdateTimer.setTimerExpirySec(UPDATE_THROTTLE_SECONDS);
- }
- else if(!mVoiceEnabled)
+
+ // Events that need to happen when a session is joined could go here.
+ // Maybe send initial spatial data?
+ notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_JOINED);
+
+ }
+ else if(!mVoiceEnabled)
{
// User bailed out during connect -- jump straight to teardown.
setState(stateSessionTerminated);
@@ -1753,21 +2181,20 @@ void LLVoiceClient::stateMachine()
{
// Only allow direct exits from this state in p2p calls (for cancelling an invite).
// Terminating a half-connected session on other types of calls seems to break something in the vivox gateway.
- if(mSessionP2P)
+ if(mAudioSession && mAudioSession->mIsP2P)
{
- sessionTerminateSendMessage();
+ sessionMediaDisconnectSendMessage(mAudioSession);
setState(stateSessionTerminated);
}
}
break;
+ //MARK: stateRunning
case stateRunning: // steady state
- // sessionTerminateSendMessage() will transition from here to stateLeavingSession
-
// Disabling voice or disconnect requested.
if(!mVoiceEnabled || mSessionTerminateRequested)
{
- sessionTerminateSendMessage();
+ leaveAudioSession();
}
else
{
@@ -1800,7 +2227,7 @@ void LLVoiceClient::stateMachine()
}
}
- if(mNonSpatialChannel)
+ if(!inSpatialChannel())
{
// When in a non-spatial channel, never send positional updates.
mSpatialCoordsDirty = false;
@@ -1813,7 +2240,7 @@ void LLVoiceClient::stateMachine()
// Send an update if the ptt state has changed (which shouldn't be able to happen that often -- the user can only click so fast)
// or every 10hz, whichever is sooner.
- if(mVolumeDirty || mPTTDirty || mSpeakerVolumeDirty || mUpdateTimer.hasExpired())
+ if((mAudioSession && mAudioSession->mVolumeDirty) || mPTTDirty || mSpeakerVolumeDirty || mUpdateTimer.hasExpired())
{
mUpdateTimer.setTimerExpirySec(UPDATE_THROTTLE_SECONDS);
sendPositionalUpdate();
@@ -1821,25 +2248,38 @@ void LLVoiceClient::stateMachine()
}
break;
+ //MARK: stateLeavingSession
case stateLeavingSession: // waiting for terminate session response
// The handler for the Session.Terminate response will transition from here to stateSessionTerminated.
break;
+ //MARK: stateSessionTerminated
case stateSessionTerminated:
- // Always reset the terminate request flag when we get here.
- mSessionTerminateRequested = false;
+ // Must do this first, since it uses mAudioSession.
notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_LEFT_CHANNEL);
+
+ if(mAudioSession)
+ {
+ sessionState *oldSession = mAudioSession;
- if(mVoiceEnabled)
+ mAudioSession = NULL;
+ // We just notified status observers about this change. Don't do it again.
+ mAudioSessionChanged = false;
+
+ // The old session may now need to be deleted.
+ reapSession(oldSession);
+ }
+ else
{
- // SPECIAL CASE: if going back to spatial but in a parcel with an empty URI, transfer the non-spatial flag now.
- // This fixes the case where you come out of a group chat in a parcel with voice disabled, and get stuck unable to rejoin spatial chat thereafter.
- if(mNextSessionSpatial && mNextSessionURI.empty())
- {
- mNonSpatialChannel = !mNextSessionSpatial;
- }
-
+ LL_WARNS("Voice") << "stateSessionTerminated with NULL mAudioSession" << LL_ENDL;
+ }
+
+ // Always reset the terminate request flag when we get here.
+ mSessionTerminateRequested = false;
+
+ if(mVoiceEnabled && !mRelogRequested)
+ {
// Just leaving a channel, go back to stateNoChannel (the "logged in but have no channel" state).
setState(stateNoChannel);
}
@@ -1847,49 +2287,71 @@ void LLVoiceClient::stateMachine()
{
// Shutting down voice, continue with disconnecting.
logout();
+
+ // The state machine will take it from here
+ mRelogRequested = false;
}
break;
+ //MARK: stateLoggingOut
case stateLoggingOut: // waiting for logout response
// The handler for the Account.Logout response will transition from here to stateLoggedOut.
break;
+ //MARK: stateLoggedOut
case stateLoggedOut: // logout response received
// shut down the connector
connectorShutdown();
break;
+ //MARK: stateConnectorStopping
case stateConnectorStopping: // waiting for connector stop
// The handler for the Connector.InitiateShutdown response will transition from here to stateConnectorStopped.
break;
+ //MARK: stateConnectorStopped
case stateConnectorStopped: // connector stop received
// Clean up and reset everything.
closeSocket();
- removeAllParticipants();
+ deleteAllSessions();
+ deleteAllBuddies();
setState(stateDisabled);
break;
+ //MARK: stateConnectorFailed
case stateConnectorFailed:
setState(stateConnectorFailedWaiting);
break;
+ //MARK: stateConnectorFailedWaiting
case stateConnectorFailedWaiting:
break;
+ //MARK: stateLoginFailed
case stateLoginFailed:
setState(stateLoginFailedWaiting);
break;
+ //MARK: stateLoginFailedWaiting
case stateLoginFailedWaiting:
// No way to recover from these. Yet.
break;
+ //MARK: stateJoinSessionFailed
case stateJoinSessionFailed:
// Transition to error state. Send out any notifications here.
- LL_WARNS("Voice") << "stateJoinSessionFailed: (" << mVivoxErrorStatusCode << "): " << mVivoxErrorStatusString << LL_ENDL;
+ if(mAudioSession)
+ {
+ LL_WARNS("Voice") << "stateJoinSessionFailed: (" << mAudioSession->mErrorStatusCode << "): " << mAudioSession->mErrorStatusString << LL_ENDL;
+ }
+ else
+ {
+ LL_WARNS("Voice") << "stateJoinSessionFailed with no current session" << LL_ENDL;
+ }
+
notifyStatusObservers(LLVoiceClientStatusObserver::ERROR_UNKNOWN);
setState(stateJoinSessionFailedWaiting);
break;
+ //MARK: stateJoinSessionFailedWaiting
case stateJoinSessionFailedWaiting:
// Joining a channel failed, either due to a failed channel name -> sip url lookup or an error from the join message.
// Region crossings may leave this state and try the join again.
@@ -1899,22 +2361,29 @@ void LLVoiceClient::stateMachine()
}
break;
+ //MARK: stateJail
case stateJail:
// We have given up. Do nothing.
break;
- case stateMicTuningNoLogin:
- // *TODO: Implement me.
- LL_WARNS("Voice") << "stateMicTuningNoLogin not handled" << LL_ENDL;
+ //MARK: stateMicTuningNoLogin
+ case stateMicTuningNoLogin:
+ // *TODO: Implement me.
+ LL_WARNS("Voice") << "stateMicTuningNoLogin not handled" << LL_ENDL;
break;
}
-
- if(mParticipantMapChanged)
+
+ if(mAudioSession && mAudioSession->mParticipantsChanged)
{
- mParticipantMapChanged = false;
- notifyObservers();
+ mAudioSession->mParticipantsChanged = false;
+ mAudioSessionChanged = true;
+ }
+
+ if(mAudioSessionChanged)
+ {
+ mAudioSessionChanged = false;
+ notifyParticipantObservers();
}
-
}
void LLVoiceClient::closeSocket(void)
@@ -1932,6 +2401,9 @@ void LLVoiceClient::loginSendMessage()
<< "" << mAccountName << ""
<< "" << mAccountPassword << ""
<< "VerifyAnswer"
+ << "true"
+ << "Application"
+ << "5"
<< "\n\n\n";
writeString(stream.str());
@@ -1939,7 +2411,10 @@ void LLVoiceClient::loginSendMessage()
void LLVoiceClient::logout()
{
- mAccountPassword = "";
+ // Ensure that we'll re-request provisioning before logging in again
+ mAccountPassword.clear();
+ mVoiceAccountServerURI.clear();
+
setState(stateLoggingOut);
logoutSendMessage();
}
@@ -1961,78 +2436,164 @@ void LLVoiceClient::logoutSendMessage()
}
}
-void LLVoiceClient::channelGetListSendMessage()
+void LLVoiceClient::accountListBlockRulesSendMessage()
{
- std::ostringstream stream;
- stream
- << ""
- << "" << mAccountHandle << ""
- << "\n\n\n";
+ if(!mAccountHandle.empty())
+ {
+ std::ostringstream stream;
- writeString(stream.str());
+ LL_DEBUGS("Voice") << "requesting block rules" << LL_ENDL;
+
+ stream
+ << ""
+ << "" << mAccountHandle << ""
+ << ""
+ << "\n\n\n";
+
+ writeString(stream.str());
+ }
+}
+
+void LLVoiceClient::accountListAutoAcceptRulesSendMessage()
+{
+ if(!mAccountHandle.empty())
+ {
+ std::ostringstream stream;
+
+ LL_DEBUGS("Voice") << "requesting auto-accept rules" << LL_ENDL;
+
+ stream
+ << ""
+ << "" << mAccountHandle << ""
+ << ""
+ << "\n\n\n";
+
+ writeString(stream.str());
+ }
}
-void LLVoiceClient::sessionCreateSendMessage()
+void LLVoiceClient::sessionGroupCreateSendMessage()
{
- LL_DEBUGS("Voice") << "requesting join: " << mNextSessionURI << LL_ENDL;
+ if(!mAccountHandle.empty())
+ {
+ std::ostringstream stream;
+
+ LL_DEBUGS("Voice") << "creating session group" << LL_ENDL;
+
+ stream
+ << ""
+ << "" << mAccountHandle << ""
+ << "Normal"
+ << ""
+ << "\n\n\n";
+
+ writeString(stream.str());
+ }
+}
- mSessionURI = mNextSessionURI;
- mNonSpatialChannel = !mNextSessionSpatial;
- mSessionResetOnClose = mNextSessionResetOnClose;
- mNextSessionResetOnClose = false;
- if(mNextSessionNoReconnect)
+void LLVoiceClient::sessionCreateSendMessage(sessionState *session, bool startAudio, bool startText)
+{
+ LL_DEBUGS("Voice") << "requesting create: " << session->mSIPURI << LL_ENDL;
+
+ session->mCreateInProgress = true;
+ if(startAudio)
{
- // Clear the stashed URI so it can't reconnect
- mNextSessionURI.clear();
+ session->mMediaConnectInProgress = true;
}
- // Only p2p sessions are created with "no reconnect".
- mSessionP2P = mNextSessionNoReconnect;
std::ostringstream stream;
stream
- << ""
+ << "mSIPURI << "\" action=\"Session.Create.1\">"
<< "" << mAccountHandle << ""
- << "" << mSessionURI << "";
+ << "" << session->mSIPURI << "";
static const std::string allowed_chars =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
"0123456789"
"-._~";
- if(!mNextSessionHash.empty())
+ if(!session->mHash.empty())
{
stream
- << "" << LLURI::escape(mNextSessionHash, allowed_chars) << ""
+ << "" << LLURI::escape(session->mHash, allowed_chars) << ""
<< "SHA1UserName";
}
stream
+ << "" << (startAudio?"true":"false") << ""
+ << "" << (startText?"true":"false") << ""
<< "" << mChannelName << ""
<< "\n\n\n";
writeString(stream.str());
}
-void LLVoiceClient::sessionConnectSendMessage()
+void LLVoiceClient::sessionGroupAddSessionSendMessage(sessionState *session, bool startAudio, bool startText)
{
- LL_DEBUGS("Voice") << "connecting to session handle: " << mNextSessionHandle << LL_ENDL;
+ LL_DEBUGS("Voice") << "requesting create: " << session->mSIPURI << LL_ENDL;
- mSessionHandle = mNextSessionHandle;
- mSessionURI = mNextP2PSessionURI;
- mNextSessionHandle.clear(); // never want to re-use these.
- mNextP2PSessionURI.clear();
- mNonSpatialChannel = !mNextSessionSpatial;
- mSessionResetOnClose = mNextSessionResetOnClose;
- mNextSessionResetOnClose = false;
- // Joining by session ID is only used to answer p2p invitations, so we know this is a p2p session.
- mSessionP2P = true;
+ session->mCreateInProgress = true;
+ if(startAudio)
+ {
+ session->mMediaConnectInProgress = true;
+ }
+
+ std::string password;
+ if(!session->mHash.empty())
+ {
+ static const std::string allowed_chars =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
+ "0123456789"
+ "-._~"
+ ;
+ password = LLURI::escape(session->mHash, allowed_chars);
+ }
+
+ std::ostringstream stream;
+ stream
+ << "mSIPURI << "\" action=\"SessionGroup.AddSession.1\">"
+ << "" << session->mGroupHandle << ""
+ << "" << session->mSIPURI << ""
+ << "" << mChannelName << ""
+ << "" << (startAudio?"true":"false") << ""
+ << "" << (startText?"true":"false") << ""
+ << "" << password << ""
+ << "SHA1UserName"
+ << "\n\n\n"
+ ;
+
+ writeString(stream.str());
+}
+
+void LLVoiceClient::sessionMediaConnectSendMessage(sessionState *session)
+{
+ LL_DEBUGS("Voice") << "connecting audio to session handle: " << session->mHandle << LL_ENDL;
+
+ session->mMediaConnectInProgress = true;
std::ostringstream stream;
+
+ stream
+ << "mHandle << "\" action=\"Session.MediaConnect.1\">"
+ << "" << session->mGroupHandle << ""
+ << "" << session->mHandle << ""
+ << "Audio"
+ << "\n\n\n";
+
+ writeString(stream.str());
+}
+
+void LLVoiceClient::sessionTextConnectSendMessage(sessionState *session)
+{
+ LL_DEBUGS("Voice") << "connecting text to session handle: " << session->mHandle << LL_ENDL;
+ std::ostringstream stream;
+
stream
- << ""
- << "" << mSessionHandle << ""
- << "default"
+ << "mHandle << "\" action=\"Session.TextConnect.1\">"
+ << "" << session->mGroupHandle << ""
+ << "" << session->mHandle << ""
<< "\n\n\n";
+
writeString(stream.str());
}
@@ -2041,52 +2602,112 @@ void LLVoiceClient::sessionTerminate()
mSessionTerminateRequested = true;
}
-void LLVoiceClient::sessionTerminateSendMessage()
+void LLVoiceClient::requestRelog()
{
- LL_DEBUGS("Voice") << "leaving session: " << mSessionURI << LL_ENDL;
+ mSessionTerminateRequested = true;
+ mRelogRequested = true;
+}
- switch(getState())
+
+void LLVoiceClient::leaveAudioSession()
+{
+ if(mAudioSession)
{
- case stateNoChannel:
- // In this case, we want to pretend the join failed so our state machine doesn't get stuck.
- // Skip the join failed transition state so we don't send out error notifications.
- setState(stateJoinSessionFailedWaiting);
- break;
- case stateJoiningSession:
- case stateSessionJoined:
- case stateRunning:
- if(!mSessionHandle.empty())
- {
- sessionTerminateByHandle(mSessionHandle);
- setState(stateLeavingSession);
- }
- else
- {
- LL_WARNS("Voice") << "called with no session handle" << LL_ENDL;
+ LL_DEBUGS("Voice") << "leaving session: " << mAudioSession->mSIPURI << LL_ENDL;
+
+ switch(getState())
+ {
+ case stateNoChannel:
+ // In this case, we want to pretend the join failed so our state machine doesn't get stuck.
+ // Skip the join failed transition state so we don't send out error notifications.
+ setState(stateJoinSessionFailedWaiting);
+ break;
+ case stateJoiningSession:
+ case stateSessionJoined:
+ case stateRunning:
+ if(!mAudioSession->mHandle.empty())
+ {
+
+#if RECORD_EVERYTHING
+ // HACK: for testing only
+ // Save looped recording
+ std::string savepath("/tmp/vivoxrecording");
+ {
+ time_t now = time(NULL);
+ const size_t BUF_SIZE = 64;
+ char time_str[BUF_SIZE]; /* Flawfinder: ignore */
+
+ strftime(time_str, BUF_SIZE, "%Y-%m-%dT%H:%M:%SZ", gmtime(&now));
+ savepath += time_str;
+ }
+ recordingLoopSave(savepath);
+#endif
+
+ sessionMediaDisconnectSendMessage(mAudioSession);
+ setState(stateLeavingSession);
+ }
+ else
+ {
+ LL_WARNS("Voice") << "called with no session handle" << LL_ENDL;
+ setState(stateSessionTerminated);
+ }
+ break;
+ case stateJoinSessionFailed:
+ case stateJoinSessionFailedWaiting:
setState(stateSessionTerminated);
- }
- break;
- case stateJoinSessionFailed:
- case stateJoinSessionFailedWaiting:
- setState(stateSessionTerminated);
- break;
-
- default:
- LL_WARNS("Voice") << "called from unknown state" << LL_ENDL;
- break;
+ break;
+
+ default:
+ LL_WARNS("Voice") << "called from unknown state" << LL_ENDL;
+ break;
+ }
+ }
+ else
+ {
+ LL_WARNS("Voice") << "called with no active session" << LL_ENDL;
+ setState(stateSessionTerminated);
}
}
-void LLVoiceClient::sessionTerminateByHandle(std::string &sessionHandle)
+void LLVoiceClient::sessionTerminateSendMessage(sessionState *session)
{
- LL_DEBUGS("Voice") << "Sending Session.Terminate with handle " << sessionHandle << LL_ENDL;
-
std::ostringstream stream;
+
+ LL_DEBUGS("Voice") << "Sending Session.Terminate with handle " << session->mHandle << LL_ENDL;
stream
<< ""
- << "" << sessionHandle << ""
- << ""
- << "\n\n\n";
+ << "" << session->mHandle << ""
+ << "\n\n\n";
+
+ writeString(stream.str());
+}
+
+void LLVoiceClient::sessionMediaDisconnectSendMessage(sessionState *session)
+{
+ std::ostringstream stream;
+
+ LL_DEBUGS("Voice") << "Sending Session.MediaDisconnect with handle " << session->mHandle << LL_ENDL;
+ stream
+ << ""
+ << "" << session->mGroupHandle << ""
+ << "" << session->mHandle << ""
+ << "Audio"
+ << "\n\n\n";
+
+ writeString(stream.str());
+
+}
+
+void LLVoiceClient::sessionTextDisconnectSendMessage(sessionState *session)
+{
+ std::ostringstream stream;
+
+ LL_DEBUGS("Voice") << "Sending Session.TextDisconnect with handle " << session->mHandle << LL_ENDL;
+ stream
+ << ""
+ << "" << session->mGroupHandle << ""
+ << "" << session->mHandle << ""
+ << "\n\n\n";
writeString(stream.str());
}
@@ -2113,14 +2734,12 @@ void LLVoiceClient::getRenderDevicesSendMessage()
void LLVoiceClient::clearCaptureDevices()
{
- // MBW -- XXX -- do something here
LL_DEBUGS("Voice") << "called" << LL_ENDL;
mCaptureDevices.clear();
}
void LLVoiceClient::addCaptureDevice(const std::string& name)
{
- // MBW -- XXX -- do something here
LL_DEBUGS("Voice") << name << LL_ENDL;
mCaptureDevices.push_back(name);
@@ -2152,15 +2771,13 @@ void LLVoiceClient::setCaptureDevice(const std::string& name)
}
void LLVoiceClient::clearRenderDevices()
-{
- // MBW -- XXX -- do something here
+{
LL_DEBUGS("Voice") << "called" << LL_ENDL;
mRenderDevices.clear();
}
void LLVoiceClient::addRenderDevice(const std::string& name)
{
- // MBW -- XXX -- do something here
LL_DEBUGS("Voice") << name << LL_ENDL;
mRenderDevices.push_back(name);
}
@@ -2272,29 +2889,22 @@ void LLVoiceClient::tuningCaptureStopSendMessage()
void LLVoiceClient::tuningSetMicVolume(float volume)
{
- int scaledVolume = ((int)(volume * 100.0f)) - 100;
- if(scaledVolume != mTuningMicVolume)
+ int scaled_volume = scale_mic_volume(volume);
+
+ if(scaled_volume != mTuningMicVolume)
{
- mTuningMicVolume = scaledVolume;
+ mTuningMicVolume = scaled_volume;
mTuningMicVolumeDirty = true;
}
}
void LLVoiceClient::tuningSetSpeakerVolume(float volume)
{
- // incoming volume has the range [0.0 ... 1.0], with 0.5 as the default.
- // Map it as follows: 0.0 -> -100, 0.5 -> 24, 1.0 -> 50
-
- volume -= 0.5f; // offset volume to the range [-0.5 ... 0.5], with 0 at the default.
- int scaledVolume = 24; // offset scaledVolume by its default level
- if(volume < 0.0f)
- scaledVolume += ((int)(volume * 248.0f)); // (24 - (-100)) * 2
- else
- scaledVolume += ((int)(volume * 52.0f)); // (50 - 24) * 2
+ int scaled_volume = scale_speaker_volume(volume);
- if(scaledVolume != mTuningSpeakerVolume)
+ if(scaled_volume != mTuningSpeakerVolume)
{
- mTuningSpeakerVolume = scaledVolume;
+ mTuningSpeakerVolume = scaled_volume;
mTuningSpeakerVolumeDirty = true;
}
}
@@ -2334,7 +2944,8 @@ void LLVoiceClient::daemonDied()
LL_WARNS("Voice") << "Connection to vivox daemon lost. Resetting state."<< LL_ENDL;
closeSocket();
- removeAllParticipants();
+ deleteAllSessions();
+ deleteAllBuddies();
// Try to relaunch the daemon
setState(stateDisabled);
@@ -2344,56 +2955,185 @@ void LLVoiceClient::giveUp()
{
// All has failed. Clean up and stop trying.
closeSocket();
- removeAllParticipants();
+ deleteAllSessions();
+ deleteAllBuddies();
setState(stateJail);
}
+static void oldSDKTransform (LLVector3 &left, LLVector3 &up, LLVector3 &at, LLVector3d &pos, LLVector3 &vel)
+{
+ F32 nat[3], nup[3], nl[3], nvel[3]; // the new at, up, left vectors and the new position and velocity
+ F64 npos[3];
+
+ // The original XML command was sent like this:
+ /*
+ << ""
+ << "" << pos[VX] << ""
+ << "" << pos[VZ] << ""
+ << "" << pos[VY] << ""
+ << ""
+ << ""
+ << "" << mAvatarVelocity[VX] << ""
+ << "" << mAvatarVelocity[VZ] << ""
+ << "" << mAvatarVelocity[VY] << ""
+ << ""
+ << ""
+ << "" << l.mV[VX] << ""
+ << "" << u.mV[VX] << ""
+ << "" << a.mV[VX] << ""
+ << ""
+ << ""
+ << "" << l.mV[VZ] << ""
+ << "" << u.mV[VY] << ""
+ << "" << a.mV[VZ] << ""
+ << ""
+ << ""
+ << "" << l.mV [VY] << ""
+ << "" << u.mV [VZ] << ""
+ << "" << a.mV [VY] << ""
+ << "";
+ */
+
+#if 1
+ // This was the original transform done when building the XML command
+ nat[0] = left.mV[VX];
+ nat[1] = up.mV[VX];
+ nat[2] = at.mV[VX];
+
+ nup[0] = left.mV[VZ];
+ nup[1] = up.mV[VY];
+ nup[2] = at.mV[VZ];
+
+ nl[0] = left.mV[VY];
+ nl[1] = up.mV[VZ];
+ nl[2] = at.mV[VY];
+
+ npos[0] = pos.mdV[VX];
+ npos[1] = pos.mdV[VZ];
+ npos[2] = pos.mdV[VY];
+
+ nvel[0] = vel.mV[VX];
+ nvel[1] = vel.mV[VZ];
+ nvel[2] = vel.mV[VY];
+
+ for(int i=0;i<3;++i) {
+ at.mV[i] = nat[i];
+ up.mV[i] = nup[i];
+ left.mV[i] = nl[i];
+ pos.mdV[i] = npos[i];
+ }
+
+ // This was the original transform done in the SDK
+ nat[0] = at.mV[2];
+ nat[1] = 0; // y component of at vector is always 0, this was up[2]
+ nat[2] = -1 * left.mV[2];
+
+ // We override whatever the application gives us
+ nup[0] = 0; // x component of up vector is always 0
+ nup[1] = 1; // y component of up vector is always 1
+ nup[2] = 0; // z component of up vector is always 0
+
+ nl[0] = at.mV[0];
+ nl[1] = 0; // y component of left vector is always zero, this was up[0]
+ nl[2] = -1 * left.mV[0];
+
+ npos[2] = pos.mdV[2] * -1.0;
+ npos[1] = pos.mdV[1];
+ npos[0] = pos.mdV[0];
+
+ for(int i=0;i<3;++i) {
+ at.mV[i] = nat[i];
+ up.mV[i] = nup[i];
+ left.mV[i] = nl[i];
+ pos.mdV[i] = npos[i];
+ }
+#else
+ // This is the compose of the two transforms (at least, that's what I'm trying for)
+ nat[0] = at.mV[VX];
+ nat[1] = 0; // y component of at vector is always 0, this was up[2]
+ nat[2] = -1 * up.mV[VZ];
+
+ // We override whatever the application gives us
+ nup[0] = 0; // x component of up vector is always 0
+ nup[1] = 1; // y component of up vector is always 1
+ nup[2] = 0; // z component of up vector is always 0
+
+ nl[0] = left.mV[VX];
+ nl[1] = 0; // y component of left vector is always zero, this was up[0]
+ nl[2] = -1 * left.mV[VY];
+
+ npos[0] = pos.mdV[VX];
+ npos[1] = pos.mdV[VZ];
+ npos[2] = pos.mdV[VY] * -1.0;
+
+ nvel[0] = vel.mV[VX];
+ nvel[1] = vel.mV[VZ];
+ nvel[2] = vel.mV[VY];
+
+ for(int i=0;i<3;++i) {
+ at.mV[i] = nat[i];
+ up.mV[i] = nup[i];
+ left.mV[i] = nl[i];
+ pos.mdV[i] = npos[i];
+ }
+
+#endif
+}
+
void LLVoiceClient::sendPositionalUpdate(void)
{
std::ostringstream stream;
if(mSpatialCoordsDirty)
{
- LLVector3 l, u, a;
+ LLVector3 l, u, a, vel;
+ LLVector3d pos;
+
+ mSpatialCoordsDirty = false;
// Always send both speaker and listener positions together.
stream << ""
- << "" << mSessionHandle << "";
+ << "" << getAudioSessionHandle() << "";
stream << "";
+// LL_DEBUGS("Voice") << "Sending speaker position " << mAvatarPosition << LL_ENDL;
l = mAvatarRot.getLeftRow();
u = mAvatarRot.getUpRow();
a = mAvatarRot.getFwdRow();
-
- LL_DEBUGS("Voice") << "Sending speaker position " << mAvatarPosition << LL_ENDL;
+ pos = mAvatarPosition;
+ vel = mAvatarVelocity;
+ // SLIM SDK: the old SDK was doing a transform on the passed coordinates that the new one doesn't do anymore.
+ // The old transform is replicated by this function.
+ oldSDKTransform(l, u, a, pos, vel);
+
stream
<< ""
- << "" << mAvatarPosition[VX] << ""
- << "" << mAvatarPosition[VZ] << ""
- << "" << mAvatarPosition[VY] << ""
+ << "" << pos.mdV[VX] << ""
+ << "" << pos.mdV[VY] << ""
+ << "" << pos.mdV[VZ] << ""
<< ""
<< ""
- << "" << mAvatarVelocity[VX] << ""
- << "" << mAvatarVelocity[VZ] << ""
- << "" << mAvatarVelocity[VY] << ""
+ << "" << vel.mV[VX] << ""
+ << "" << vel.mV[VY] << ""
+ << "" << vel.mV[VZ] << ""
<< ""
<< ""
- << "" << l.mV[VX] << ""
- << "" << u.mV[VX] << ""
- << "" << a.mV[VX] << ""
+ << "" << a.mV[VX] << ""
+ << "" << a.mV[VY] << ""
+ << "" << a.mV[VZ] << ""
<< ""
<< ""
- << "" << l.mV[VZ] << ""
+ << "" << u.mV[VX] << ""
<< "" << u.mV[VY] << ""
- << "" << a.mV[VZ] << ""
+ << "" << u.mV[VZ] << ""
<< ""
<< ""
- << "" << l.mV [VY] << ""
- << "" << u.mV [VZ] << ""
- << "" << a.mV [VY] << ""
+ << "" << l.mV [VX] << ""
+ << "" << l.mV [VY] << ""
+ << "" << l.mV [VZ] << ""
<< "";
stream << "";
@@ -2429,166 +3169,499 @@ void LLVoiceClient::sendPositionalUpdate(void)
l = earRot.getLeftRow();
u = earRot.getUpRow();
a = earRot.getFwdRow();
+ pos = earPosition;
+ vel = earVelocity;
- LL_DEBUGS("Voice") << "Sending listener position " << earPosition << LL_ENDL;
-
+// LL_DEBUGS("Voice") << "Sending listener position " << earPosition << LL_ENDL;
+
+ oldSDKTransform(l, u, a, pos, vel);
+
stream
<< ""
- << "" << earPosition[VX] << ""
- << "" << earPosition[VZ] << ""
- << "" << earPosition[VY] << ""
+ << "" << pos.mdV[VX] << ""
+ << "" << pos.mdV[VY] << ""
+ << "" << pos.mdV[VZ] << ""
<< ""
<< ""
- << "" << earVelocity[VX] << ""
- << "" << earVelocity[VZ] << ""
- << "" << earVelocity[VY] << ""
+ << "" << vel.mV[VX] << ""
+ << "" << vel.mV[VY] << ""
+ << "" << vel.mV[VZ] << ""
<< ""
<< ""
- << "" << l.mV[VX] << ""
- << "" << u.mV[VX] << ""
- << "" << a.mV[VX] << ""
+ << "" << a.mV[VX] << ""
+ << "" << a.mV[VY] << ""
+ << "" << a.mV[VZ] << ""
<< ""
<< ""
- << "" << l.mV[VZ] << ""
+ << "" << u.mV[VX] << ""
<< "" << u.mV[VY] << ""
- << "" << a.mV[VZ] << ""
+ << "" << u.mV[VZ] << ""
<< ""
<< ""
- << "" << l.mV [VY] << ""
- << "" << u.mV [VZ] << ""
- << "" << a.mV [VY] << ""
+ << "" << l.mV [VX] << ""
+ << "" << l.mV [VY] << ""
+ << "" << l.mV [VZ] << ""
<< "";
+
stream << "";
stream << "\n\n\n";
}
-
- if(mPTTDirty)
+
+ if(mAudioSession && mAudioSession->mVolumeDirty)
{
- // Send a local mute command.
- // NOTE that the state of "PTT" is the inverse of "local mute".
- // (i.e. when PTT is true, we send a mute command with "false", and vice versa)
-
- LL_DEBUGS("Voice") << "Sending MuteLocalMic command with parameter " << (mPTT?"false":"true") << LL_ENDL;
-
- stream << ""
- << "" << mConnectorHandle << ""
- << "" << (mPTT?"false":"true") << ""
- << "\n\n\n";
+ participantMap::iterator iter = mAudioSession->mParticipantsByURI.begin();
- }
-
- if(mVolumeDirty)
- {
- participantMap::iterator iter = mParticipantMap.begin();
+ mAudioSession->mVolumeDirty = false;
- for(; iter != mParticipantMap.end(); iter++)
+ for(; iter != mAudioSession->mParticipantsByURI.end(); iter++)
{
participantState *p = iter->second;
if(p->mVolumeDirty)
{
- int volume = p->mOnMuteList?0:p->mUserVolume;
-
- LL_INFOS("Voice") << "Setting volume for avatar " << p->mAvatarID << " to " << volume << LL_ENDL;
-
- // Send a mute/unumte command for the user (actually "volume for me").
- stream << ""
- << "" << mSessionHandle << ""
- << "" << p->mURI << ""
- << "" << volume << ""
- << "\n\n\n";
+ // Can't set volume/mute for yourself
+ if(!p->mIsSelf)
+ {
+ int volume = p->mUserVolume;
+ bool mute = p->mOnMuteList;
+
+ // SLIM SDK: scale volume from 0-400 (with 100 as "normal") to 0-100 (with 56 as "normal")
+ if(volume < 100)
+ volume = (volume * 56) / 100;
+ else
+ volume = (((volume - 100) * (100 - 56)) / 300) + 56;
+
+ if(mute)
+ {
+ // SetParticipantMuteForMe doesn't work in p2p sessions.
+ // If we want the user to be muted, set their volume to 0 as well.
+ // This isn't perfect, but it will at least reduce their volume to a minimum.
+ volume = 0;
+ }
+
+ if(volume == 0)
+ mute = true;
+
+ LL_DEBUGS("Voice") << "Setting volume/mute for avatar " << p->mAvatarID << " to " << volume << (mute?"/true":"/false") << LL_ENDL;
+
+ // SLIM SDK: Send both volume and mute commands.
+
+ // Send a "volume for me" command for the user.
+ stream << ""
+ << "" << getAudioSessionHandle() << ""
+ << "" << p->mURI << ""
+ << "" << volume << ""
+ << "\n\n\n";
+ // Send a "mute for me" command for the user
+ stream << ""
+ << "" << getAudioSessionHandle() << ""
+ << "" << p->mURI << ""
+ << "" << (mute?"1":"0") << ""
+ << "\n\n\n";
+ }
+
p->mVolumeDirty = false;
}
}
}
+
+ buildLocalAudioUpdates(stream);
+
+ if(!stream.str().empty())
+ {
+ writeString(stream.str());
+ }
+ // Friends list updates can be huge, especially on the first voice login of an account with lots of friends.
+ // Batching them all together can choke SLVoice, so send them in separate writes.
+ sendFriendsListUpdates();
+}
+
+void LLVoiceClient::buildSetCaptureDevice(std::ostringstream &stream)
+{
+ if(mCaptureDeviceDirty)
+ {
+ LL_DEBUGS("Voice") << "Setting input device = \"" << mCaptureDevice << "\"" << LL_ENDL;
+
+ stream
+ << ""
+ << "" << mCaptureDevice << ""
+ << ""
+ << "\n\n\n";
+
+ mCaptureDeviceDirty = false;
+ }
+}
+
+void LLVoiceClient::buildSetRenderDevice(std::ostringstream &stream)
+{
+ if(mRenderDeviceDirty)
+ {
+ LL_DEBUGS("Voice") << "Setting output device = \"" << mRenderDevice << "\"" << LL_ENDL;
+
+ stream
+ << ""
+ << "" << mRenderDevice << ""
+ << ""
+ << "\n\n\n";
+ mRenderDeviceDirty = false;
+ }
+}
+
+void LLVoiceClient::buildLocalAudioUpdates(std::ostringstream &stream)
+{
+ buildSetCaptureDevice(stream);
+
+ buildSetRenderDevice(stream);
+
+ if(mPTTDirty)
+ {
+ mPTTDirty = false;
+
+ // Send a local mute command.
+ // NOTE that the state of "PTT" is the inverse of "local mute".
+ // (i.e. when PTT is true, we send a mute command with "false", and vice versa)
+
+ LL_DEBUGS("Voice") << "Sending MuteLocalMic command with parameter " << (mPTT?"false":"true") << LL_ENDL;
+
+ stream << ""
+ << "" << mConnectorHandle << ""
+ << "" << (mPTT?"false":"true") << ""
+ << "\n\n\n";
+
+ }
+
if(mSpeakerMuteDirty)
{
- const char *muteval = ((mSpeakerVolume == -100)?"true":"false");
+ const char *muteval = ((mSpeakerVolume == 0)?"true":"false");
+
+ mSpeakerMuteDirty = false;
+
LL_INFOS("Voice") << "Setting speaker mute to " << muteval << LL_ENDL;
stream << ""
<< "" << mConnectorHandle << ""
<< "" << muteval << ""
- << "\n\n\n";
+ << "\n\n\n";
+
}
if(mSpeakerVolumeDirty)
{
+ mSpeakerVolumeDirty = false;
+
LL_INFOS("Voice") << "Setting speaker volume to " << mSpeakerVolume << LL_ENDL;
stream << ""
<< "" << mConnectorHandle << ""
<< "" << mSpeakerVolume << ""
- << "\n\n\n";
+ << "\n\n\n";
+
}
if(mMicVolumeDirty)
{
+ mMicVolumeDirty = false;
+
LL_INFOS("Voice") << "Setting mic volume to " << mMicVolume << LL_ENDL;
stream << ""
<< "" << mConnectorHandle << ""
<< "" << mMicVolume << ""
- << "\n\n\n";
+ << "\n\n\n";
}
- // MBW -- XXX -- Maybe check to make sure the capture/render devices are in the current list here?
- if(mCaptureDeviceDirty)
- {
- buildSetCaptureDevice(stream);
- }
+}
- if(mRenderDeviceDirty)
+void LLVoiceClient::checkFriend(const LLUUID& id)
+{
+ std::string name;
+ buddyListEntry *buddy = findBuddy(id);
+
+ // Make sure we don't add a name before it's been looked up.
+ if(gCacheName->getFullName(id, name))
{
- buildSetRenderDevice(stream);
+
+ const LLRelationship* relationInfo = LLAvatarTracker::instance().getBuddyInfo(id);
+ bool canSeeMeOnline = false;
+ if(relationInfo && relationInfo->isRightGrantedTo(LLRelationship::GRANT_ONLINE_STATUS))
+ canSeeMeOnline = true;
+
+ // When we get here, mNeedsSend is true and mInSLFriends is false. Change them as necessary.
+
+ if(buddy)
+ {
+ // This buddy is already in both lists.
+
+ if(name != buddy->mDisplayName)
+ {
+ // The buddy is in the list with the wrong name. Update it with the correct name.
+ LL_WARNS("Voice") << "Buddy " << id << " has wrong name (\"" << buddy->mDisplayName << "\" should be \"" << name << "\"), updating."<< LL_ENDL;
+ buddy->mDisplayName = name;
+ buddy->mNeedsNameUpdate = true; // This will cause the buddy to be resent.
+ }
+ }
+ else
+ {
+ // This buddy was not in the vivox list, needs to be added.
+ buddy = addBuddy(sipURIFromID(id), name);
+ buddy->mUUID = id;
+ }
+
+ // In all the above cases, the buddy is in the SL friends list (which is how we got here).
+ buddy->mInSLFriends = true;
+ buddy->mCanSeeMeOnline = canSeeMeOnline;
+ buddy->mNameResolved = true;
+
}
-
- mSpatialCoordsDirty = false;
- mPTTDirty = false;
- mVolumeDirty = false;
- mSpeakerVolumeDirty = false;
- mMicVolumeDirty = false;
- mSpeakerMuteDirty = false;
- mCaptureDeviceDirty = false;
- mRenderDeviceDirty = false;
-
- if(!stream.str().empty())
+ else
{
- writeString(stream.str());
+ // This name hasn't been looked up yet. Don't do anything with this buddy list entry until it has.
+ if(buddy)
+ {
+ buddy->mNameResolved = false;
+ }
+
+ // Initiate a lookup.
+ // The "lookup completed" callback will ensure that the friends list is rechecked after it completes.
+ lookupName(id);
}
}
-void LLVoiceClient::buildSetCaptureDevice(std::ostringstream &stream)
+void LLVoiceClient::clearAllLists()
{
- LL_DEBUGS("Voice") << "Setting input device = \"" << mCaptureDevice << "\"" << LL_ENDL;
+ // FOR TESTING ONLY
- stream
- << ""
- << "" << mCaptureDevice << ""
- << ""
- << "\n\n\n";
+ // This will send the necessary commands to delete ALL buddies, autoaccept rules, and block rules SLVoice tells us about.
+ buddyListMap::iterator buddy_it;
+ for(buddy_it = mBuddyListMap.begin(); buddy_it != mBuddyListMap.end();)
+ {
+ buddyListEntry *buddy = buddy_it->second;
+ buddy_it++;
+
+ std::ostringstream stream;
+
+ if(buddy->mInVivoxBuddies)
+ {
+ // delete this entry from the vivox buddy list
+ buddy->mInVivoxBuddies = false;
+ LL_DEBUGS("Voice") << "delete " << buddy->mURI << " (" << buddy->mDisplayName << ")" << LL_ENDL;
+ stream << ""
+ << "" << mAccountHandle << ""
+ << "" << buddy->mURI << ""
+ << "\n\n\n";
+ }
+
+ if(buddy->mHasBlockListEntry)
+ {
+ // Delete the associated block list entry (so the block list doesn't fill up with junk)
+ buddy->mHasBlockListEntry = false;
+ stream << ""
+ << "" << mAccountHandle << ""
+ << "" << buddy->mURI << ""
+ << "\n\n\n";
+ }
+ if(buddy->mHasAutoAcceptListEntry)
+ {
+ // Delete the associated auto-accept list entry (so the auto-accept list doesn't fill up with junk)
+ buddy->mHasAutoAcceptListEntry = false;
+ stream << ""
+ << "" << mAccountHandle << ""
+ << "" << buddy->mURI << ""
+ << "\n\n\n";
+ }
+
+ writeString(stream.str());
+
+ }
}
-void LLVoiceClient::buildSetRenderDevice(std::ostringstream &stream)
+void LLVoiceClient::sendFriendsListUpdates()
{
- LL_DEBUGS("Voice") << "Setting output device = \"" << mRenderDevice << "\"" << LL_ENDL;
+ if(mBuddyListMapPopulated && mBlockRulesListReceived && mAutoAcceptRulesListReceived && mFriendsListDirty)
+ {
+ mFriendsListDirty = false;
+
+ if(0)
+ {
+ // FOR TESTING ONLY -- clear all buddy list, block list, and auto-accept list entries.
+ clearAllLists();
+ return;
+ }
+
+ LL_INFOS("Voice") << "Checking vivox buddy list against friends list..." << LL_ENDL;
+
+ buddyListMap::iterator buddy_it;
+ for(buddy_it = mBuddyListMap.begin(); buddy_it != mBuddyListMap.end(); buddy_it++)
+ {
+ // reset the temp flags in the local buddy list
+ buddy_it->second->mInSLFriends = false;
+ }
+
+ // correlate with the friends list
+ {
+ LLCollectAllBuddies collect;
+ LLAvatarTracker::instance().applyFunctor(collect);
+ LLCollectAllBuddies::buddy_map_t::const_iterator it = collect.mOnline.begin();
+ LLCollectAllBuddies::buddy_map_t::const_iterator end = collect.mOnline.end();
+
+ for ( ; it != end; ++it)
+ {
+ checkFriend(it->second);
+ }
+ it = collect.mOffline.begin();
+ end = collect.mOffline.end();
+ for ( ; it != end; ++it)
+ {
+ checkFriend(it->second);
+ }
+ }
+
+ LL_INFOS("Voice") << "Sending friend list updates..." << LL_ENDL;
- stream
- << ""
- << "" << mRenderDevice << ""
- << ""
- << "\n\n\n";
+ for(buddy_it = mBuddyListMap.begin(); buddy_it != mBuddyListMap.end();)
+ {
+ buddyListEntry *buddy = buddy_it->second;
+ buddy_it++;
+
+ // Ignore entries that aren't resolved yet.
+ if(buddy->mNameResolved)
+ {
+ std::ostringstream stream;
+
+ if(buddy->mInSLFriends && (!buddy->mInVivoxBuddies || buddy->mNeedsNameUpdate))
+ {
+ if(mNumberOfAliases > 0)
+ {
+ // Add (or update) this entry in the vivox buddy list
+ buddy->mInVivoxBuddies = true;
+ buddy->mNeedsNameUpdate = false;
+ LL_DEBUGS("Voice") << "add/update " << buddy->mURI << " (" << buddy->mDisplayName << ")" << LL_ENDL;
+ stream
+ << ""
+ << "" << mAccountHandle << ""
+ << "" << buddy->mURI << ""
+ << "" << buddy->mDisplayName << ""
+ << "" // Without this, SLVoice doesn't seem to parse the command.
+ << "0"
+ << "\n\n\n";
+ }
+ }
+ else if(!buddy->mInSLFriends)
+ {
+ // This entry no longer exists in your SL friends list. Remove all traces of it from the Vivox buddy list.
+ if(buddy->mInVivoxBuddies)
+ {
+ // delete this entry from the vivox buddy list
+ buddy->mInVivoxBuddies = false;
+ LL_DEBUGS("Voice") << "delete " << buddy->mURI << " (" << buddy->mDisplayName << ")" << LL_ENDL;
+ stream << ""
+ << "" << mAccountHandle << ""
+ << "" << buddy->mURI << ""
+ << "\n\n\n";
+ }
+
+ if(buddy->mHasBlockListEntry)
+ {
+ // Delete the associated block list entry, if any
+ buddy->mHasBlockListEntry = false;
+ stream << ""
+ << "" << mAccountHandle << ""
+ << "" << buddy->mURI << ""
+ << "\n\n\n";
+ }
+ if(buddy->mHasAutoAcceptListEntry)
+ {
+ // Delete the associated auto-accept list entry, if any
+ buddy->mHasAutoAcceptListEntry = false;
+ stream << ""
+ << "" << mAccountHandle << ""
+ << "" << buddy->mURI << ""
+ << "\n\n\n";
+ }
+ }
+
+ if(buddy->mInSLFriends)
+ {
+
+ if(buddy->mCanSeeMeOnline)
+ {
+ // Buddy should not be blocked.
+
+ // If this buddy doesn't already have either a block or autoaccept list entry, we'll update their status when we receive a SubscriptionEvent.
+
+ // If the buddy has a block list entry, delete it.
+ if(buddy->mHasBlockListEntry)
+ {
+ buddy->mHasBlockListEntry = false;
+ stream << ""
+ << "" << mAccountHandle << ""
+ << "" << buddy->mURI << ""
+ << "\n\n\n";
+
+
+ // If we just deleted a block list entry, add an auto-accept entry.
+ if(!buddy->mHasAutoAcceptListEntry)
+ {
+ buddy->mHasAutoAcceptListEntry = true;
+ stream << ""
+ << "" << mAccountHandle << ""
+ << "" << buddy->mURI << ""
+ << "0"
+ << "\n\n\n";
+ }
+ }
+ }
+ else
+ {
+ // Buddy should be blocked.
+
+ // If this buddy doesn't already have either a block or autoaccept list entry, we'll update their status when we receive a SubscriptionEvent.
+
+ // If this buddy has an autoaccept entry, delete it
+ if(buddy->mHasAutoAcceptListEntry)
+ {
+ buddy->mHasAutoAcceptListEntry = false;
+ stream << ""
+ << "" << mAccountHandle << ""
+ << "" << buddy->mURI << ""
+ << "\n\n\n";
+
+ // If we just deleted an auto-accept entry, add a block list entry.
+ if(!buddy->mHasBlockListEntry)
+ {
+ buddy->mHasBlockListEntry = true;
+ stream << ""
+ << "" << mAccountHandle << ""
+ << "" << buddy->mURI << ""
+ << "1"
+ << "\n\n\n";
+ }
+ }
+ }
+
+ if(!buddy->mInSLFriends && !buddy->mInVivoxBuddies)
+ {
+ // Delete this entry from the local buddy list. This should NOT invalidate the iterator,
+ // since it has already been incremented to the next entry.
+ deleteBuddy(buddy->mURI);
+ }
+
+ }
+ writeString(stream.str());
+ }
+ }
+ }
}
/////////////////////////////
// Response/Event handlers
-void LLVoiceClient::connectorCreateResponse(int statusCode, std::string &statusString, std::string &connectorHandle)
+void LLVoiceClient::connectorCreateResponse(int statusCode, std::string &statusString, std::string &connectorHandle, std::string &versionID)
{
if(statusCode != 0)
{
@@ -2598,6 +3671,7 @@ void LLVoiceClient::connectorCreateResponse(int statusCode, std::string &statusS
else
{
// Connector created, move forward.
+ LL_INFOS("Voice") << "Connector.Create succeeded, Vivox SDK version is " << versionID << LL_ENDL;
mConnectorHandle = connectorHandle;
if(getState() == stateConnectorStarting)
{
@@ -2606,7 +3680,7 @@ void LLVoiceClient::connectorCreateResponse(int statusCode, std::string &statusS
}
}
-void LLVoiceClient::loginResponse(int statusCode, std::string &statusString, std::string &accountHandle)
+void LLVoiceClient::loginResponse(int statusCode, std::string &statusString, std::string &accountHandle, int numberOfAliases)
{
LL_DEBUGS("Voice") << "Account.Login response (" << statusCode << "): " << statusString << LL_ENDL;
@@ -2627,7 +3701,8 @@ void LLVoiceClient::loginResponse(int statusCode, std::string &statusString, std
{
// Login succeeded, move forward.
mAccountHandle = accountHandle;
- // MBW -- XXX -- This needs to wait until the LoginStateChangeEvent is received.
+ mNumberOfAliases = numberOfAliases;
+ // This needs to wait until the AccountLoginStateChangeEvent is received.
// if(getState() == stateLoggingIn)
// {
// setState(stateLoggedIn);
@@ -2635,106 +3710,91 @@ void LLVoiceClient::loginResponse(int statusCode, std::string &statusString, std
}
}
-void LLVoiceClient::channelGetListResponse(int statusCode, std::string &statusString)
-{
+void LLVoiceClient::sessionCreateResponse(std::string &requestId, int statusCode, std::string &statusString, std::string &sessionHandle)
+{
+ sessionState *session = findSessionBeingCreatedByURI(requestId);
+
+ if(session)
+ {
+ session->mCreateInProgress = false;
+ }
+
if(statusCode != 0)
{
- LL_WARNS("Voice") << "Account.ChannelGetList response failure: " << statusString << LL_ENDL;
- switchChannel();
+ LL_WARNS("Voice") << "Session.Create response failure (" << statusCode << "): " << statusString << LL_ENDL;
+ if(session)
+ {
+ session->mErrorStatusCode = statusCode;
+ session->mErrorStatusString = statusString;
+ if(session == mAudioSession)
+ {
+ setState(stateJoinSessionFailed);
+ }
+ else
+ {
+ reapSession(session);
+ }
+ }
}
else
{
- // Got the channel list, try to do a lookup.
- std::string uri = findChannelURI(mChannelName);
- if(uri.empty())
- {
- // Lookup failed, can't join a channel for this area.
- LL_INFOS("Voice") << "failed to map channel name: " << mChannelName << LL_ENDL;
- }
- else
+ LL_INFOS("Voice") << "Session.Create response received (success), session handle is " << sessionHandle << LL_ENDL;
+ if(session)
{
- // We have a sip URL for this area.
- LL_INFOS("Voice") << "mapped channel " << mChannelName << " to URI "<< uri << LL_ENDL;
+ setSessionHandle(session, sessionHandle);
}
-
- // switchChannel with an empty uri string will do the right thing (leave channel and not rejoin)
- switchChannel(uri);
}
}
-void LLVoiceClient::sessionCreateResponse(int statusCode, std::string &statusString, std::string &sessionHandle)
+void LLVoiceClient::sessionGroupAddSessionResponse(std::string &requestId, int statusCode, std::string &statusString, std::string &sessionHandle)
{
+ sessionState *session = findSessionBeingCreatedByURI(requestId);
+
+ if(session)
+ {
+ session->mCreateInProgress = false;
+ }
+
if(statusCode != 0)
{
- LL_WARNS("Voice") << "Session.Create response failure (" << statusCode << "): " << statusString << LL_ENDL;
-// if(statusCode == 1015)
-// {
-// if(getState() == stateJoiningSession)
-// {
-// // this happened during a real join. Going to sessionTerminated should cause a retry in appropriate cases.
-// LL_WARNS("Voice") << "session handle \"" << sessionHandle << "\", mSessionStateEventHandle \"" << mSessionStateEventHandle << "\""<< LL_ENDL;
-// if(!sessionHandle.empty())
-// {
-// // This session is bad. Terminate it.
-// mSessionHandle = sessionHandle;
-// sessionTerminateByHandle(sessionHandle);
-// setState(stateLeavingSession);
-// }
-// else if(!mSessionStateEventHandle.empty())
-// {
-// mSessionHandle = mSessionStateEventHandle;
-// sessionTerminateByHandle(mSessionStateEventHandle);
-// setState(stateLeavingSession);
-// }
-// else
-// {
-// setState(stateSessionTerminated);
-// }
-// }
-// else
-// {
-// // We didn't think we were in the middle of a join. Don't change state.
-// LL_WARNS("Voice") << "Not in stateJoiningSession, ignoring" << LL_ENDL;
-// }
-// }
-// else
+ LL_WARNS("Voice") << "SessionGroup.AddSession response failure (" << statusCode << "): " << statusString << LL_ENDL;
+ if(session)
{
- mVivoxErrorStatusCode = statusCode;
- mVivoxErrorStatusString = statusString;
- setState(stateJoinSessionFailed);
+ session->mErrorStatusCode = statusCode;
+ session->mErrorStatusString = statusString;
+ if(session == mAudioSession)
+ {
+ setState(stateJoinSessionFailed);
+ }
+ else
+ {
+ reapSession(session);
+ }
}
}
else
{
- LL_DEBUGS("Voice") << "Session.Create response received (success), session handle is " << sessionHandle << LL_ENDL;
- if(getState() == stateJoiningSession)
- {
- // This is also grabbed in the SessionStateChangeEvent handler, but it might be useful to have it early...
- mSessionHandle = sessionHandle;
- }
- else
+ LL_DEBUGS("Voice") << "SessionGroup.AddSession response received (success), session handle is " << sessionHandle << LL_ENDL;
+ if(session)
{
- // We should never get a session.create response in any state except stateJoiningSession. Things are out of sync. Kill this session.
- sessionTerminateByHandle(sessionHandle);
+ setSessionHandle(session, sessionHandle);
}
}
}
-void LLVoiceClient::sessionConnectResponse(int statusCode, std::string &statusString)
+void LLVoiceClient::sessionConnectResponse(std::string &requestId, int statusCode, std::string &statusString)
{
+ sessionState *session = findSession(requestId);
if(statusCode != 0)
{
LL_WARNS("Voice") << "Session.Connect response failure (" << statusCode << "): " << statusString << LL_ENDL;
-// if(statusCode == 1015)
-// {
-// LL_WARNS("Voice") << "terminating existing session" << LL_ENDL;
-// sessionTerminate();
-// }
-// else
+ if(session)
{
- mVivoxErrorStatusCode = statusCode;
- mVivoxErrorStatusString = statusString;
- setState(stateJoinSessionFailed);
+ session->mMediaConnectInProgress = false;
+ session->mErrorStatusCode = statusCode;
+ session->mErrorStatusString = statusString;
+ if(session == mAudioSession)
+ setState(stateJoinSessionFailed);
}
}
else
@@ -2743,27 +3803,12 @@ void LLVoiceClient::sessionConnectResponse(int statusCode, std::string &statusSt
}
}
-void LLVoiceClient::sessionTerminateResponse(int statusCode, std::string &statusString)
-{
- if(statusCode != 0)
- {
- LL_WARNS("Voice") << "Session.Terminate response failure: (" << statusCode << "): " << statusString << LL_ENDL;
- if(getState() == stateLeavingSession)
- {
- // This is probably "(404): Server reporting Failure. Not a member of this conference."
- // Do this so we don't get stuck.
- setState(stateSessionTerminated);
- }
- }
-
-}
-
void LLVoiceClient::logoutResponse(int statusCode, std::string &statusString)
{
if(statusCode != 0)
{
LL_WARNS("Voice") << "Account.Logout response failure: " << statusString << LL_ENDL;
- // MBW -- XXX -- Should this ever fail? do we care if it does?
+ // Should this ever fail? do we care if it does?
}
if(getState() == stateLoggingOut)
@@ -2777,7 +3822,7 @@ void LLVoiceClient::connectorShutdownResponse(int statusCode, std::string &statu
if(statusCode != 0)
{
LL_WARNS("Voice") << "Connector.InitiateShutdown response failure: " << statusString << LL_ENDL;
- // MBW -- XXX -- Should this ever fail? do we care if it does?
+ // Should this ever fail? do we care if it does?
}
mConnected = false;
@@ -2788,109 +3833,277 @@ void LLVoiceClient::connectorShutdownResponse(int statusCode, std::string &statu
}
}
-void LLVoiceClient::sessionStateChangeEvent(
+void LLVoiceClient::sessionAddedEvent(
std::string &uriString,
- int statusCode,
- std::string &statusString,
- std::string &sessionHandle,
- int state,
+ std::string &alias,
+ std::string &sessionHandle,
+ std::string &sessionGroupHandle,
bool isChannel,
- std::string &nameString)
+ bool incoming,
+ std::string &nameString,
+ std::string &applicationString)
{
- switch(state)
- {
- case 4: // I see this when joining the session
- LL_INFOS("Voice") << "joined session " << uriString << ", name " << nameString << " handle " << mNextSessionHandle << LL_ENDL;
+ sessionState *session = NULL;
- // Session create succeeded, move forward.
- mSessionStateEventHandle = sessionHandle;
- mSessionStateEventURI = uriString;
- if(sessionHandle == mSessionHandle)
+ LL_INFOS("Voice") << "session " << uriString << ", alias " << alias << ", name " << nameString << " handle " << sessionHandle << LL_ENDL;
+
+ session = addSession(uriString, sessionHandle);
+ if(session)
+ {
+ session->mGroupHandle = sessionGroupHandle;
+ session->mIsChannel = isChannel;
+ session->mIncoming = incoming;
+ session->mAlias = alias;
+
+ // Generate a caller UUID -- don't need to do this for channels
+ if(!session->mIsChannel)
+ {
+ if(IDFromName(session->mSIPURI, session->mCallerID))
{
- // This is the session we're joining.
- if(getState() == stateJoiningSession)
- {
- setState(stateSessionJoined);
- //RN: the uriString being returned by vivox here is actually your account uri, not the channel
- // you are attempting to join, so ignore it
- //LL_DEBUGS("Voice") << "received URI " << uriString << "(previously " << mSessionURI << ")" << LL_ENDL;
- //mSessionURI = uriString;
- }
+ // Normal URI(base64-encoded UUID)
}
- else if(sessionHandle == mNextSessionHandle)
+ else if(!session->mAlias.empty() && IDFromName(session->mAlias, session->mCallerID))
{
-// LL_DEBUGS("Voice") << "received URI " << uriString << ", name " << nameString << " for next session (handle " << mNextSessionHandle << ")" << LL_ENDL;
+ // Wrong URI, but an alias is available. Stash the incoming URI as an alternate
+ session->mAlternateSIPURI = session->mSIPURI;
+
+ // and generate a proper URI from the ID.
+ setSessionURI(session, sipURIFromID(session->mCallerID));
}
else
{
- LL_WARNS("Voice") << "joining unknown session handle " << sessionHandle << ", URI " << uriString << ", name " << nameString << LL_ENDL;
- // MBW -- XXX -- Should we send a Session.Terminate here?
- }
-
- break;
- case 5: // I see this when leaving the session
- LL_INFOS("Voice") << "left session " << uriString << ", name " << nameString << " handle " << mNextSessionHandle << LL_ENDL;
+ LL_INFOS("Voice") << "Could not generate caller id from uri, using hash of uri " << session->mSIPURI << LL_ENDL;
+ setUUIDFromStringHash(session->mCallerID, session->mSIPURI);
+ session->mSynthesizedCallerID = true;
+
+ // Can't look up the name in this case -- we have to extract it from the URI.
+ std::string namePortion = nameFromsipURI(session->mSIPURI);
+ if(namePortion.empty())
+ {
+ // Didn't seem to be a SIP URI, just use the whole provided name.
+ namePortion = nameString;
+ }
+
+ // Some incoming names may be separated with an underscore instead of a space. Fix this.
+ LLStringUtil::replaceChar(namePortion, '_', ' ');
+
+ // Act like we just finished resolving the name (this stores it in all the right places)
+ avatarNameResolved(session->mCallerID, namePortion);
+ }
+
+ LL_INFOS("Voice") << "caller ID: " << session->mCallerID << LL_ENDL;
+
+ if(!session->mSynthesizedCallerID)
+ {
+ // If we got here, we don't have a proper name. Initiate a lookup.
+ lookupName(session->mCallerID);
+ }
+ }
+ }
+}
+
+void LLVoiceClient::sessionGroupAddedEvent(std::string &sessionGroupHandle)
+{
+ LL_DEBUGS("Voice") << "handle " << sessionGroupHandle << LL_ENDL;
+
+#if USE_SESSION_GROUPS
+ if(mMainSessionGroupHandle.empty())
+ {
+ // This is the first (i.e. "main") session group. Save its handle.
+ mMainSessionGroupHandle = sessionGroupHandle;
+ }
+ else
+ {
+ LL_DEBUGS("Voice") << "Already had a session group handle " << mMainSessionGroupHandle << LL_ENDL;
+ }
+#endif
+}
+
+void LLVoiceClient::joinedAudioSession(sessionState *session)
+{
+ if(mAudioSession != session)
+ {
+ sessionState *oldSession = mAudioSession;
+
+ mAudioSession = session;
+ mAudioSessionChanged = true;
+
+ // The old session may now need to be deleted.
+ reapSession(oldSession);
+ }
+
+ // This is the session we're joining.
+ if(getState() == stateJoiningSession)
+ {
+ setState(stateSessionJoined);
+
+ // SLIM SDK: we don't always receive a participant state change for ourselves when joining a channel now.
+ // Add the current user as a participant here.
+ participantState *participant = session->addParticipant(sipURIFromName(mAccountName));
+ if(participant)
+ {
+ participant->mIsSelf = true;
+ lookupName(participant->mAvatarID);
- // Set the session handle to the empty string. If we get back to stateJoiningSession, we'll want to wait for the new session handle.
- if(sessionHandle == mSessionHandle)
+ LL_INFOS("Voice") << "added self as participant \"" << participant->mAccountName
+ << "\" (" << participant->mAvatarID << ")"<< LL_ENDL;
+ }
+
+ if(!session->mIsChannel)
+ {
+ // this is a p2p session. Make sure the other end is added as a participant.
+ participantState *participant = session->addParticipant(session->mSIPURI);
+ if(participant)
{
- // MBW -- XXX -- I think this is no longer necessary, now that we've got mNextSessionURI/mNextSessionHandle
- // mSessionURI.clear();
- // clear the session handle here just for sanity.
- mSessionHandle.clear();
- if(mSessionResetOnClose)
+ if(participant->mAvatarIDValid)
{
- mSessionResetOnClose = false;
- mNonSpatialChannel = false;
- mNextSessionSpatial = true;
- parcelChanged();
+ lookupName(participant->mAvatarID);
+ }
+ else if(!session->mName.empty())
+ {
+ participant->mDisplayName = session->mName;
+ avatarNameResolved(participant->mAvatarID, session->mName);
}
-
- removeAllParticipants();
- switch(getState())
+ // TODO: Question: Do we need to set up mAvatarID/mAvatarIDValid here?
+ LL_INFOS("Voice") << "added caller as participant \"" << participant->mAccountName
+ << "\" (" << participant->mAvatarID << ")"<< LL_ENDL;
+ }
+ }
+ }
+}
+
+void LLVoiceClient::sessionRemovedEvent(
+ std::string &sessionHandle,
+ std::string &sessionGroupHandle)
+{
+ LL_INFOS("Voice") << "handle " << sessionHandle << LL_ENDL;
+
+ sessionState *session = findSession(sessionHandle);
+ if(session)
+ {
+ leftAudioSession(session);
+
+ // This message invalidates the session's handle. Set it to empty.
+ setSessionHandle(session);
+
+ // Reset the media state (we now have no info)
+ session->mMediaStreamState = streamStateUnknown;
+ session->mTextStreamState = streamStateUnknown;
+
+ // Conditionally delete the session
+ reapSession(session);
+ }
+ else
+ {
+ LL_WARNS("Voice") << "unknown session " << sessionHandle << " removed" << LL_ENDL;
+ }
+}
+
+void LLVoiceClient::reapSession(sessionState *session)
+{
+ if(session)
+ {
+ if(!session->mHandle.empty())
+ {
+ LL_DEBUGS("Voice") << "NOT deleting session " << session->mSIPURI << " (non-null session handle)" << LL_ENDL;
+ }
+ else if(session->mCreateInProgress)
+ {
+ LL_DEBUGS("Voice") << "NOT deleting session " << session->mSIPURI << " (create in progress)" << LL_ENDL;
+ }
+ else if(session->mMediaConnectInProgress)
+ {
+ LL_DEBUGS("Voice") << "NOT deleting session " << session->mSIPURI << " (connect in progress)" << LL_ENDL;
+ }
+ else if(session == mAudioSession)
+ {
+ LL_DEBUGS("Voice") << "NOT deleting session " << session->mSIPURI << " (it's the current session)" << LL_ENDL;
+ }
+ else if(session == mNextAudioSession)
+ {
+ LL_DEBUGS("Voice") << "NOT deleting session " << session->mSIPURI << " (it's the next session)" << LL_ENDL;
+ }
+ else
+ {
+ // TODO: Question: Should we check for queued text messages here?
+ // We don't have a reason to keep tracking this session, so just delete it.
+ LL_DEBUGS("Voice") << "deleting session " << session->mSIPURI << LL_ENDL;
+ deleteSession(session);
+ session = NULL;
+ }
+ }
+ else
+ {
+// LL_DEBUGS("Voice") << "session is NULL" << LL_ENDL;
+ }
+}
+
+// Returns true if the session seems to indicate we've moved to a region on a different voice server
+bool LLVoiceClient::sessionNeedsRelog(sessionState *session)
+{
+ bool result = false;
+
+ if(session != NULL)
+ {
+ // Only make this check for spatial channels (so it won't happen for group or p2p calls)
+ if(session->mIsSpatial)
+ {
+ std::string::size_type atsign;
+
+ atsign = session->mSIPURI.find("@");
+
+ if(atsign != std::string::npos)
+ {
+ std::string urihost = session->mSIPURI.substr(atsign + 1);
+ if(stricmp(urihost.c_str(), mVoiceSIPURIHostName.c_str()))
{
- case stateJoiningSession:
- case stateSessionJoined:
- case stateRunning:
- case stateLeavingSession:
- case stateJoinSessionFailed:
- case stateJoinSessionFailedWaiting:
- // normal transition
- LL_INFOS("Voice") << "left session " << sessionHandle << "in state " << state2string(getState()) << LL_ENDL;
- setState(stateSessionTerminated);
- break;
+ // The hostname in this URI is different from what we expect. This probably means we need to relog.
- case stateSessionTerminated:
- // this will happen sometimes -- there are cases where we send the terminate and then go straight to this state.
- LL_WARNS("Voice") << "left session " << sessionHandle << "in state " << state2string(getState()) << LL_ENDL;
- break;
+ // We could make a ProvisionVoiceAccountRequest and compare the result with the current values of
+ // mVoiceSIPURIHostName and mVoiceAccountServerURI to be really sure, but this is a pretty good indicator.
- default:
- LL_WARNS("Voice") << "unexpected SessionStateChangeEvent (left session) in state " << state2string(getState()) << LL_ENDL;
- setState(stateSessionTerminated);
- break;
+ result = true;
}
-
- // store status values for later notification of observers
- mVivoxErrorStatusCode = statusCode;
- mVivoxErrorStatusString = statusString;
- }
- else
- {
- LL_INFOS("Voice") << "leaving unknown session handle " << sessionHandle << ", URI " << uriString << ", name " << nameString << LL_ENDL;
}
+ }
+ }
+
+ return result;
+}
- mSessionStateEventHandle.clear();
- mSessionStateEventURI.clear();
- break;
- default:
- LL_WARNS("Voice") << "unknown state: " << state << LL_ENDL;
- break;
+void LLVoiceClient::leftAudioSession(
+ sessionState *session)
+{
+ if(mAudioSession == session)
+ {
+ switch(getState())
+ {
+ case stateJoiningSession:
+ case stateSessionJoined:
+ case stateRunning:
+ case stateLeavingSession:
+ case stateJoinSessionFailed:
+ case stateJoinSessionFailedWaiting:
+ // normal transition
+ LL_DEBUGS("Voice") << "left session " << session->mHandle << " in state " << state2string(getState()) << LL_ENDL;
+ setState(stateSessionTerminated);
+ break;
+
+ case stateSessionTerminated:
+ // this will happen sometimes -- there are cases where we send the terminate and then go straight to this state.
+ LL_WARNS("Voice") << "left session " << session->mHandle << " in state " << state2string(getState()) << LL_ENDL;
+ break;
+
+ default:
+ LL_WARNS("Voice") << "unexpected SessionStateChangeEvent (left session) in state " << state2string(getState()) << LL_ENDL;
+ setState(stateSessionTerminated);
+ break;
+ }
}
}
-void LLVoiceClient::loginStateChangeEvent(
+void LLVoiceClient::accountLoginStateChangeEvent(
std::string &accountHandle,
int statusCode,
std::string &statusString,
@@ -2923,313 +4136,821 @@ void LLVoiceClient::loginStateChangeEvent(
}
}
-void LLVoiceClient::sessionNewEvent(
- std::string &accountHandle,
- std::string &eventSessionHandle,
- int state,
- std::string &nameString,
- std::string &uriString)
+void LLVoiceClient::mediaStreamUpdatedEvent(
+ std::string &sessionHandle,
+ std::string &sessionGroupHandle,
+ int statusCode,
+ std::string &statusString,
+ int state,
+ bool incoming)
{
- LL_DEBUGS("Voice") << "state is " << state << LL_ENDL;
+ sessionState *session = findSession(sessionHandle);
- switch(state)
+ LL_DEBUGS("Voice") << "session " << sessionHandle << ", status code " << statusCode << ", string \"" << statusString << "\"" << LL_ENDL;
+
+ if(session)
{
- case 0:
- {
- LL_DEBUGS("Voice") << "session handle = " << eventSessionHandle << ", name = " << nameString << ", uri = " << uriString << LL_ENDL;
+ // We know about this session
+
+ // Save the state for later use
+ session->mMediaStreamState = state;
+
+ switch(statusCode)
+ {
+ case 0:
+ case 200:
+ // generic success
+ // Don't change the saved error code (it may have been set elsewhere)
+ break;
+ default:
+ // save the status code for later
+ session->mErrorStatusCode = statusCode;
+ break;
+ }
+
+ switch(state)
+ {
+ case streamStateIdle:
+ // Standard "left audio session"
+ session->mVoiceEnabled = false;
+ session->mMediaConnectInProgress = false;
+ leftAudioSession(session);
+ break;
+
+ case streamStateConnected:
+ session->mVoiceEnabled = true;
+ session->mMediaConnectInProgress = false;
+ joinedAudioSession(session);
+ break;
+
+ case streamStateRinging:
+ if(incoming)
+ {
+ // Send the voice chat invite to the GUI layer
+ // TODO: Question: Should we correlate with the mute list here?
+ session->mIMSessionID = LLIMMgr::computeSessionID(IM_SESSION_P2P_INVITE, session->mCallerID);
+ session->mVoiceInvitePending = true;
+ if(session->mName.empty())
+ {
+ lookupName(session->mCallerID);
+ }
+ else
+ {
+ // Act like we just finished resolving the name
+ avatarNameResolved(session->mCallerID, session->mName);
+ }
+ }
+ break;
+
+ default:
+ LL_WARNS("Voice") << "unknown state " << state << LL_ENDL;
+ break;
+
+ }
+
+ }
+ else
+ {
+ LL_WARNS("Voice") << "session " << sessionHandle << "not found"<< LL_ENDL;
+ }
+}
- LLUUID caller_id;
- if(IDFromName(nameString, caller_id))
+void LLVoiceClient::textStreamUpdatedEvent(
+ std::string &sessionHandle,
+ std::string &sessionGroupHandle,
+ bool enabled,
+ int state,
+ bool incoming)
+{
+ sessionState *session = findSession(sessionHandle);
+
+ if(session)
+ {
+ // Save the state for later use
+ session->mTextStreamState = state;
+
+ // We know about this session
+ switch(state)
+ {
+ case 0: // We see this when the text stream closes
+ LL_DEBUGS("Voice") << "stream closed" << LL_ENDL;
+ break;
+
+ case 1: // We see this on an incoming call from the Connector
+ // Try to send any text messages queued for this session.
+ sendQueuedTextMessages(session);
+
+ // Send the text chat invite to the GUI layer
+ // TODO: Question: Should we correlate with the mute list here?
+ session->mTextInvitePending = true;
+ if(session->mName.empty())
{
- gIMMgr->inviteToSession(
- LLIMMgr::computeSessionID(
- IM_SESSION_P2P_INVITE,
- caller_id),
- LLStringUtil::null,
- caller_id,
- LLStringUtil::null,
- IM_SESSION_P2P_INVITE,
- LLIMMgr::INVITATION_TYPE_VOICE,
- eventSessionHandle);
+ lookupName(session->mCallerID);
}
else
{
- LL_WARNS("Voice") << "Could not generate caller id from uri " << uriString << LL_ENDL;
+ // Act like we just finished resolving the name
+ avatarNameResolved(session->mCallerID, session->mName);
}
- }
- break;
-
- default:
- LL_WARNS("Voice") << "unknown state: " << state << LL_ENDL;
- break;
+ break;
+
+ default:
+ LL_WARNS("Voice") << "unknown state " << state << LL_ENDL;
+ break;
+
+ }
}
}
-void LLVoiceClient::participantStateChangeEvent(
+void LLVoiceClient::participantAddedEvent(
+ std::string &sessionHandle,
+ std::string &sessionGroupHandle,
std::string &uriString,
- int statusCode,
- std::string &statusString,
- int state,
+ std::string &alias,
std::string &nameString,
std::string &displayNameString,
int participantType)
{
- participantState *participant = NULL;
- LL_DEBUGS("Voice") << "state is " << state << LL_ENDL;
-
- switch(state)
+ sessionState *session = findSession(sessionHandle);
+ if(session)
{
- case 7: // I see this when a participant joins
- participant = addParticipant(uriString);
- if(participant)
+ participantState *participant = session->addParticipant(uriString);
+ if(participant)
+ {
+ participant->mAccountName = nameString;
+
+ LL_DEBUGS("Voice") << "added participant \"" << participant->mAccountName
+ << "\" (" << participant->mAvatarID << ")"<< LL_ENDL;
+
+ if(participant->mAvatarIDValid)
{
- participant->mName = nameString;
- LL_DEBUGS("Voice") << "added participant \"" << participant->mName
- << "\" (" << participant->mAvatarID << ")"<< LL_ENDL;
+ // Initiate a lookup
+ lookupName(participant->mAvatarID);
}
- break;
- case 9: // I see this when a participant leaves
- participant = findParticipant(uriString);
- if(participant)
+ else
{
- removeParticipant(participant);
+ // If we don't have a valid avatar UUID, we need to fill in the display name to make the active speakers floater work.
+ std::string namePortion = nameFromsipURI(uriString);
+ if(namePortion.empty())
+ {
+ // Problem with the SIP URI, fall back to the display name
+ namePortion = displayNameString;
+ }
+ if(namePortion.empty())
+ {
+ // Problems with both of the above, fall back to the account name
+ namePortion = nameString;
+ }
+
+ // Set the display name (which is a hint to the active speakers window not to do its own lookup)
+ participant->mDisplayName = namePortion;
+ avatarNameResolved(participant->mAvatarID, namePortion);
}
- break;
- default:
- LL_DEBUGS("Voice") << "unknown state: " << state << LL_ENDL;
- break;
+ }
}
}
-void LLVoiceClient::participantPropertiesEvent(
+void LLVoiceClient::participantRemovedEvent(
+ std::string &sessionHandle,
+ std::string &sessionGroupHandle,
std::string &uriString,
- int statusCode,
- std::string &statusString,
- bool isLocallyMuted,
- bool isModeratorMuted,
- bool isSpeaking,
- int volume,
- F32 energy)
+ std::string &alias,
+ std::string &nameString)
{
- participantState *participant = findParticipant(uriString);
- if(participant)
+ sessionState *session = findSession(sessionHandle);
+ if(session)
{
- participant->mPTT = !isLocallyMuted;
- participant->mIsSpeaking = isSpeaking;
- participant->mIsModeratorMuted = isModeratorMuted;
- if (isSpeaking)
+ participantState *participant = session->findParticipant(uriString);
+ if(participant)
+ {
+ session->removeParticipant(participant);
+ }
+ else
{
- participant->mSpeakingTimeout.reset();
+ LL_DEBUGS("Voice") << "unknown participant " << uriString << LL_ENDL;
}
- participant->mPower = energy;
- participant->mVolume = volume;
}
else
{
- LL_WARNS("Voice") << "unknown participant: " << uriString << LL_ENDL;
+ LL_DEBUGS("Voice") << "unknown session " << sessionHandle << LL_ENDL;
}
}
-void LLVoiceClient::auxAudioPropertiesEvent(F32 energy)
-{
- LL_DEBUGS("Voice") << "got energy " << energy << LL_ENDL;
- mTuningEnergy = energy;
-}
-void LLVoiceClient::muteListChanged()
+void LLVoiceClient::participantUpdatedEvent(
+ std::string &sessionHandle,
+ std::string &sessionGroupHandle,
+ std::string &uriString,
+ std::string &alias,
+ bool isModeratorMuted,
+ bool isSpeaking,
+ int volume,
+ F32 energy)
{
- // The user's mute list has been updated. Go through the current participant list and sync it with the mute list.
-
- participantMap::iterator iter = mParticipantMap.begin();
-
- for(; iter != mParticipantMap.end(); iter++)
+ sessionState *session = findSession(sessionHandle);
+ if(session)
{
- participantState *p = iter->second;
+ participantState *participant = session->findParticipant(uriString);
- // Check to see if this participant is on the mute list already
- updateMuteState(p);
- }
-}
+ if(participant)
+ {
+ participant->mIsSpeaking = isSpeaking;
+ participant->mIsModeratorMuted = isModeratorMuted;
-/////////////////////////////
-// Managing list of participants
-LLVoiceClient::participantState::participantState(const std::string &uri) :
- mURI(uri), mPTT(false), mIsSpeaking(false), mIsModeratorMuted(false), mLastSpokeTimestamp(0.f), mPower(0.f), mVolume(0), mServiceType(serviceTypeUnknown),
- mOnMuteList(false), mUserVolume(100), mVolumeDirty(false), mAvatarIDValid(false)
-{
+ // SLIM SDK: convert range: ensure that energy is set to zero if is_speaking is false
+ if (isSpeaking)
+ {
+ participant->mSpeakingTimeout.reset();
+ participant->mPower = energy;
+ }
+ else
+ {
+ participant->mPower = 0.0f;
+ }
+ participant->mVolume = volume;
+ }
+ else
+ {
+ LL_WARNS("Voice") << "unknown participant: " << uriString << LL_ENDL;
+ }
+ }
+ else
+ {
+ LL_INFOS("Voice") << "unknown session " << sessionHandle << LL_ENDL;
+ }
}
-LLVoiceClient::participantState *LLVoiceClient::addParticipant(const std::string &uri)
+void LLVoiceClient::buddyPresenceEvent(
+ std::string &uriString,
+ std::string &alias,
+ std::string &statusString,
+ std::string &applicationString)
{
- participantState *result = NULL;
-
- participantMap::iterator iter = mParticipantMap.find(uri);
+ buddyListEntry *buddy = findBuddy(uriString);
- if(iter != mParticipantMap.end())
+ if(buddy)
{
- // Found a matching participant already in the map.
- result = iter->second;
- }
+ LL_DEBUGS("Voice") << "Presence event for " << buddy->mDisplayName << " status \"" << statusString << "\", application \"" << applicationString << "\""<< LL_ENDL;
+ LL_DEBUGS("Voice") << "before: mOnlineSL = " << (buddy->mOnlineSL?"true":"false") << ", mOnlineSLim = " << (buddy->mOnlineSLim?"true":"false") << LL_ENDL;
- if(!result)
- {
- // participant isn't already in one list or the other.
- result = new participantState(uri);
- mParticipantMap.insert(participantMap::value_type(uri, result));
- mParticipantMapChanged = true;
-
- // Try to do a reverse transform on the URI to get the GUID back.
+ if(applicationString.empty())
{
- LLUUID id;
- if(IDFromName(uri, id))
- {
- result->mAvatarIDValid = true;
- result->mAvatarID = id;
+ // This presence event is from a client that doesn't set up the Application string. Do things the old-skool way.
+ // NOTE: this will be needed to support people who aren't on the 3010-class SDK yet.
- updateMuteState(result);
+ if ( stricmp("Unknown", statusString.c_str())== 0)
+ {
+ // User went offline with a non-SLim-enabled viewer.
+ buddy->mOnlineSL = false;
+ }
+ else if ( stricmp("Online", statusString.c_str())== 0)
+ {
+ // User came online with a non-SLim-enabled viewer.
+ buddy->mOnlineSL = true;
+ }
+ else
+ {
+ // If the user is online through SLim, their status will be "Online-slc", "Away", or something else.
+ // NOTE: we should never see this unless someone is running an OLD version of SLim -- the versions that should be in use now all set the application string.
+ buddy->mOnlineSLim = true;
+ }
+ }
+ else if(applicationString.find("SecondLifeViewer") != std::string::npos)
+ {
+ // This presence event is from a viewer that sets the application string
+ if ( stricmp("Unknown", statusString.c_str())== 0)
+ {
+ // Viewer says they're offline
+ buddy->mOnlineSL = false;
+ }
+ else
+ {
+ // Viewer says they're online
+ buddy->mOnlineSL = true;
}
}
+ else
+ {
+ // This presence event is from something which is NOT the SL viewer (assume it's SLim).
+ if ( stricmp("Unknown", statusString.c_str())== 0)
+ {
+ // SLim says they're offline
+ buddy->mOnlineSLim = false;
+ }
+ else
+ {
+ // SLim says they're online
+ buddy->mOnlineSLim = true;
+ }
+ }
+
+ LL_DEBUGS("Voice") << "after: mOnlineSL = " << (buddy->mOnlineSL?"true":"false") << ", mOnlineSLim = " << (buddy->mOnlineSLim?"true":"false") << LL_ENDL;
- LL_DEBUGS("Voice") << "participant \"" << result->mURI << "\" added." << LL_ENDL;
+ // HACK -- increment the internal change serial number in the LLRelationship (without changing the actual status), so the UI notices the change.
+ LLAvatarTracker::instance().setBuddyOnline(buddy->mUUID,LLAvatarTracker::instance().isBuddyOnline(buddy->mUUID));
+
+ notifyFriendObservers();
}
-
- return result;
+ else
+ {
+ LL_DEBUGS("Voice") << "Presence for unknown buddy " << uriString << LL_ENDL;
+ }
}
-void LLVoiceClient::updateMuteState(participantState *p)
+void LLVoiceClient::messageEvent(
+ std::string &sessionHandle,
+ std::string &uriString,
+ std::string &alias,
+ std::string &messageHeader,
+ std::string &messageBody,
+ std::string &applicationString)
{
- if(p->mAvatarIDValid)
+ LL_DEBUGS("Voice") << "Message event, session " << sessionHandle << " from " << uriString << LL_ENDL;
+// LL_DEBUGS("Voice") << " header " << messageHeader << ", body: \n" << messageBody << LL_ENDL;
+
+ if(messageHeader.find("text/html") != std::string::npos)
{
- bool isMuted = LLMuteList::getInstance()->isMuted(p->mAvatarID, LLMute::flagVoiceChat);
- if(p->mOnMuteList != isMuted)
+ std::string rawMessage;
+
{
- p->mOnMuteList = isMuted;
- p->mVolumeDirty = true;
- mVolumeDirty = true;
+ const std::string startMarker = ", try looking for a instead.
+ start = messageBody.find(startSpan);
+ start = messageBody.find(startMarker2, start);
+ end = messageBody.find(endSpan);
+
+ if(start != std::string::npos)
+ {
+ start += startMarker2.size();
+
+ if(end != std::string::npos)
+ end -= start;
+
+ rawMessage.assign(messageBody, start, end);
+ }
+ }
+ }
+
+// LL_DEBUGS("Voice") << " raw message = \n" << rawMessage << LL_ENDL;
+
+ // strip formatting tags
+ {
+ std::string::size_type start;
+ std::string::size_type end;
+
+ while((start = rawMessage.find('<')) != std::string::npos)
+ {
+ if((end = rawMessage.find('>', start + 1)) != std::string::npos)
+ {
+ // Strip out the tag
+ rawMessage.erase(start, (end + 1) - start);
+ }
+ else
+ {
+ // Avoid an infinite loop
+ break;
+ }
+ }
+ }
+
+ // Decode ampersand-escaped chars
+ {
+ std::string::size_type mark = 0;
+
+ // The text may contain text encoded with <, >, and &
+ mark = 0;
+ while((mark = rawMessage.find("<", mark)) != std::string::npos)
+ {
+ rawMessage.replace(mark, 4, "<");
+ mark += 1;
+ }
+
+ mark = 0;
+ while((mark = rawMessage.find(">", mark)) != std::string::npos)
+ {
+ rawMessage.replace(mark, 4, ">");
+ mark += 1;
+ }
+
+ mark = 0;
+ while((mark = rawMessage.find("&", mark)) != std::string::npos)
+ {
+ rawMessage.replace(mark, 5, "&");
+ mark += 1;
+ }
}
+
+ // strip leading/trailing whitespace (since we always seem to get a couple newlines)
+ LLStringUtil::trim(rawMessage);
+
+// LL_DEBUGS("Voice") << " stripped message = \n" << rawMessage << LL_ENDL;
+
+ sessionState *session = findSession(sessionHandle);
+ if(session)
+ {
+ bool is_busy = gAgent.getBusy();
+ bool is_muted = LLMuteList::getInstance()->isMuted(session->mCallerID, session->mName, LLMute::flagTextChat);
+ bool is_linden = LLMuteList::getInstance()->isLinden(session->mName);
+ bool quiet_chat = false;
+ LLChat chat;
+
+ chat.mMuted = is_muted && !is_linden;
+
+ if(!chat.mMuted)
+ {
+ chat.mFromID = session->mCallerID;
+ chat.mFromName = session->mName;
+ chat.mSourceType = CHAT_SOURCE_AGENT;
+
+ if(is_busy && !is_linden)
+ {
+ quiet_chat = true;
+ // TODO: Question: Return busy mode response here? Or maybe when session is started instead?
+ }
+
+ std::string fullMessage = std::string(": ") + rawMessage;
+
+ LL_DEBUGS("Voice") << "adding message, name " << session->mName << " session " << session->mIMSessionID << ", target " << session->mCallerID << LL_ENDL;
+ gIMMgr->addMessage(session->mIMSessionID,
+ session->mCallerID,
+ session->mName.c_str(),
+ fullMessage.c_str(),
+ LLStringUtil::null, // default arg
+ IM_NOTHING_SPECIAL, // default arg
+ 0, // default arg
+ LLUUID::null, // default arg
+ LLVector3::zero, // default arg
+ true); // prepend name and make it a link to the user's profile
+
+ chat.mText = std::string("IM: ") + session->mName + std::string(": ") + rawMessage;
+ // If the chat should come in quietly (i.e. we're in busy mode), pretend it's from a local agent.
+ LLFloaterChat::addChat( chat, TRUE, quiet_chat );
+ }
+ }
}
}
-void LLVoiceClient::removeParticipant(LLVoiceClient::participantState *participant)
+void LLVoiceClient::sessionNotificationEvent(std::string &sessionHandle, std::string &uriString, std::string ¬ificationType)
{
- if(participant)
+ sessionState *session = findSession(sessionHandle);
+
+ if(session)
{
- participantMap::iterator iter = mParticipantMap.find(participant->mURI);
-
- LL_DEBUGS("Voice") << "participant \"" << participant->mURI << "\" (" << participant->mAvatarID << ") removed." << LL_ENDL;
+ participantState *participant = session->findParticipant(uriString);
+ if(participant)
+ {
+ if (!stricmp(notificationType.c_str(), "Typing"))
+ {
+ // Other end started typing
+ // TODO: The proper way to add a typing notification seems to be LLIMMgr::processIMTypingStart().
+ // It requires an LLIMInfo for the message, which we don't have here.
+ }
+ else if (!stricmp(notificationType.c_str(), "NotTyping"))
+ {
+ // Other end stopped typing
+ // TODO: The proper way to remove a typing notification seems to be LLIMMgr::processIMTypingStop().
+ // It requires an LLIMInfo for the message, which we don't have here.
+ }
+ else
+ {
+ LL_DEBUGS("Voice") << "Unknown notification type " << notificationType << "for participant " << uriString << " in session " << session->mSIPURI << LL_ENDL;
+ }
+ }
+ else
+ {
+ LL_DEBUGS("Voice") << "Unknown participant " << uriString << " in session " << session->mSIPURI << LL_ENDL;
+ }
+ }
+ else
+ {
+ LL_DEBUGS("Voice") << "Unknown session handle " << sessionHandle << LL_ENDL;
+ }
+}
- mParticipantMap.erase(iter);
- delete participant;
- mParticipantMapChanged = true;
+void LLVoiceClient::subscriptionEvent(std::string &buddyURI, std::string &subscriptionHandle, std::string &alias, std::string &displayName, std::string &applicationString, std::string &subscriptionType)
+{
+ buddyListEntry *buddy = findBuddy(buddyURI);
+
+ if(!buddy)
+ {
+ // Couldn't find buddy by URI, try converting the alias...
+ if(!alias.empty())
+ {
+ LLUUID id;
+ if(IDFromName(alias, id))
+ {
+ buddy = findBuddy(id);
+ }
+ }
+ }
+
+ if(buddy)
+ {
+ std::ostringstream stream;
+
+ if(buddy->mCanSeeMeOnline)
+ {
+ // Sending the response will create an auto-accept rule
+ buddy->mHasAutoAcceptListEntry = true;
+ }
+ else
+ {
+ // Sending the response will create a block rule
+ buddy->mHasBlockListEntry = true;
+ }
+
+ if(buddy->mInSLFriends)
+ {
+ buddy->mInVivoxBuddies = true;
+ }
+
+ stream
+ << ""
+ << "" << mAccountHandle << ""
+ << "" << buddy->mURI << ""
+ << "" << (buddy->mCanSeeMeOnline?"Allow":"Hide") << ""
+ << ""<< (buddy->mInSLFriends?"1":"0")<< ""
+ << "" << subscriptionHandle << ""
+ << ""
+ << "\n\n\n";
+
+ writeString(stream.str());
}
}
-void LLVoiceClient::removeAllParticipants()
+void LLVoiceClient::auxAudioPropertiesEvent(F32 energy)
+{
+ LL_DEBUGS("Voice") << "got energy " << energy << LL_ENDL;
+ mTuningEnergy = energy;
+}
+
+void LLVoiceClient::buddyListChanged()
{
- LL_DEBUGS("Voice") << "called" << LL_ENDL;
+ // This is called after we receive a BuddyAndGroupListChangedEvent.
+ mBuddyListMapPopulated = true;
+ mFriendsListDirty = true;
+}
- while(!mParticipantMap.empty())
+void LLVoiceClient::muteListChanged()
+{
+ // The user's mute list has been updated. Go through the current participant list and sync it with the mute list.
+ if(mAudioSession)
{
- removeParticipant(mParticipantMap.begin()->second);
+ participantMap::iterator iter = mAudioSession->mParticipantsByURI.begin();
+
+ for(; iter != mAudioSession->mParticipantsByURI.end(); iter++)
+ {
+ participantState *p = iter->second;
+
+ // Check to see if this participant is on the mute list already
+ if(p->updateMuteState())
+ mAudioSession->mVolumeDirty = true;
+ }
}
}
-LLVoiceClient::participantMap *LLVoiceClient::getParticipantList(void)
+void LLVoiceClient::updateFriends(U32 mask)
{
- return &mParticipantMap;
+ if(mask & (LLFriendObserver::ADD | LLFriendObserver::REMOVE | LLFriendObserver::POWERS))
+ {
+ // Just resend the whole friend list to the daemon
+ mFriendsListDirty = true;
+ }
}
+/////////////////////////////
+// Managing list of participants
+LLVoiceClient::participantState::participantState(const std::string &uri) :
+ mURI(uri),
+ mPTT(false),
+ mIsSpeaking(false),
+ mIsModeratorMuted(false),
+ mLastSpokeTimestamp(0.f),
+ mPower(0.f),
+ mVolume(0),
+ mOnMuteList(false),
+ mUserVolume(100),
+ mVolumeDirty(false),
+ mAvatarIDValid(false),
+ mIsSelf(false)
+{
+}
-LLVoiceClient::participantState *LLVoiceClient::findParticipant(const std::string &uri)
+LLVoiceClient::participantState *LLVoiceClient::sessionState::addParticipant(const std::string &uri)
{
participantState *result = NULL;
+ bool useAlternateURI = false;
- // Don't find any participants if we're not connected. This is so that we don't continue to get stale data
- // after the daemon dies.
- if(mConnected)
+ // Note: this is mostly the body of LLVoiceClient::sessionState::findParticipant(), but since we need to know if it
+ // matched the alternate SIP URI (so we can add it properly), we need to reproduce it here.
{
- participantMap::iterator iter = mParticipantMap.find(uri);
-
- if(iter != mParticipantMap.end())
+ participantMap::iterator iter = mParticipantsByURI.find(&uri);
+
+ if(iter == mParticipantsByURI.end())
+ {
+ if(!mAlternateSIPURI.empty() && (uri == mAlternateSIPURI))
+ {
+ // This is a p2p session (probably with the SLIM client) with an alternate URI for the other participant.
+ // Use mSIPURI instead, since it will be properly encoded.
+ iter = mParticipantsByURI.find(&(mSIPURI));
+ useAlternateURI = true;
+ }
+ }
+
+ if(iter != mParticipantsByURI.end())
{
result = iter->second;
}
}
-
+
+ if(!result)
+ {
+ // participant isn't already in one list or the other.
+ result = new participantState(useAlternateURI?mSIPURI:uri);
+ mParticipantsByURI.insert(participantMap::value_type(&(result->mURI), result));
+ mParticipantsChanged = true;
+
+ // Try to do a reverse transform on the URI to get the GUID back.
+ {
+ LLUUID id;
+ if(IDFromName(result->mURI, id))
+ {
+ result->mAvatarIDValid = true;
+ result->mAvatarID = id;
+
+ if(result->updateMuteState())
+ mVolumeDirty = true;
+ }
+ else
+ {
+ // Create a UUID by hashing the URI, but do NOT set mAvatarIDValid.
+ // This tells both code in LLVoiceClient and code in llfloateractivespeakers.cpp that the ID will not be in the name cache.
+ setUUIDFromStringHash(result->mAvatarID, uri);
+ }
+ }
+
+ mParticipantsByUUID.insert(participantUUIDMap::value_type(&(result->mAvatarID), result));
+
+ LL_DEBUGS("Voice") << "participant \"" << result->mURI << "\" added." << LL_ENDL;
+ }
+
return result;
}
-
-LLVoiceClient::participantState *LLVoiceClient::findParticipantByAvatar(LLVOAvatar *avatar)
+bool LLVoiceClient::participantState::updateMuteState()
{
- participantState * result = NULL;
-
- // You'd think this would work, but it doesn't...
-// std::string uri = sipURIFromAvatar(avatar);
-
- // Currently, the URI is just the account name.
- std::string loginName = nameFromAvatar(avatar);
- result = findParticipant(loginName);
+ bool result = false;
- if(result != NULL)
+ if(mAvatarIDValid)
{
- if(!result->mAvatarIDValid)
+ bool isMuted = LLMuteList::getInstance()->isMuted(mAvatarID, LLMute::flagVoiceChat);
+ if(mOnMuteList != isMuted)
{
- result->mAvatarID = avatar->getID();
- result->mAvatarIDValid = true;
-
- // We just figured out the avatar ID, so the participant list has "changed" from the perspective of anyone who uses that to identify participants.
- mParticipantMapChanged = true;
-
- updateMuteState(result);
+ mOnMuteList = isMuted;
+ mVolumeDirty = true;
+ result = true;
}
-
-
}
-
return result;
}
-LLVoiceClient::participantState* LLVoiceClient::findParticipantByID(const LLUUID& id)
+void LLVoiceClient::sessionState::removeParticipant(LLVoiceClient::participantState *participant)
{
- participantState * result = NULL;
+ if(participant)
+ {
+ participantMap::iterator iter = mParticipantsByURI.find(&(participant->mURI));
+ participantUUIDMap::iterator iter2 = mParticipantsByUUID.find(&(participant->mAvatarID));
+
+ LL_DEBUGS("Voice") << "participant \"" << participant->mURI << "\" (" << participant->mAvatarID << ") removed." << LL_ENDL;
+
+ if(iter == mParticipantsByURI.end())
+ {
+ LL_ERRS("Voice") << "Internal error: participant " << participant->mURI << " not in URI map" << LL_ENDL;
+ }
+ else if(iter2 == mParticipantsByUUID.end())
+ {
+ LL_ERRS("Voice") << "Internal error: participant ID " << participant->mAvatarID << " not in UUID map" << LL_ENDL;
+ }
+ else if(iter->second != iter2->second)
+ {
+ LL_ERRS("Voice") << "Internal error: participant mismatch!" << LL_ENDL;
+ }
+ else
+ {
+ mParticipantsByURI.erase(iter);
+ mParticipantsByUUID.erase(iter2);
+
+ delete participant;
+ mParticipantsChanged = true;
+ }
+ }
+}
+
+void LLVoiceClient::sessionState::removeAllParticipants()
+{
+ LL_DEBUGS("Voice") << "called" << LL_ENDL;
- // Currently, the URI is just the account name.
- std::string loginName = nameFromID(id);
- result = findParticipant(loginName);
+ while(!mParticipantsByURI.empty())
+ {
+ removeParticipant(mParticipantsByURI.begin()->second);
+ }
+
+ if(!mParticipantsByUUID.empty())
+ {
+ LL_ERRS("Voice") << "Internal error: empty URI map, non-empty UUID map" << LL_ENDL
+ }
+}
+LLVoiceClient::participantMap *LLVoiceClient::getParticipantList(void)
+{
+ participantMap *result = NULL;
+ if(mAudioSession)
+ {
+ result = &(mAudioSession->mParticipantsByURI);
+ }
return result;
}
-void LLVoiceClient::clearChannelMap(void)
+LLVoiceClient::participantState *LLVoiceClient::sessionState::findParticipant(const std::string &uri)
{
- mChannelMap.clear();
+ participantState *result = NULL;
+
+ participantMap::iterator iter = mParticipantsByURI.find(&uri);
+
+ if(iter == mParticipantsByURI.end())
+ {
+ if(!mAlternateSIPURI.empty() && (uri == mAlternateSIPURI))
+ {
+ // This is a p2p session (probably with the SLIM client) with an alternate URI for the other participant.
+ // Look up the other URI
+ iter = mParticipantsByURI.find(&(mSIPURI));
+ }
+ }
+
+ if(iter != mParticipantsByURI.end())
+ {
+ result = iter->second;
+ }
+
+ return result;
}
-void LLVoiceClient::addChannelMapEntry(std::string &name, std::string &uri)
+LLVoiceClient::participantState* LLVoiceClient::sessionState::findParticipantByID(const LLUUID& id)
{
- LL_DEBUGS("Voice") << "Adding channel name mapping: " << name << " -> " << uri << LL_ENDL;
- mChannelMap.insert(channelMap::value_type(name, uri));
+ participantState * result = NULL;
+ participantUUIDMap::iterator iter = mParticipantsByUUID.find(&id);
+
+ if(iter != mParticipantsByUUID.end())
+ {
+ result = iter->second;
+ }
+
+ return result;
}
-std::string LLVoiceClient::findChannelURI(std::string &name)
+LLVoiceClient::participantState* LLVoiceClient::findParticipantByID(const LLUUID& id)
{
- std::string result;
+ participantState * result = NULL;
- channelMap::iterator iter = mChannelMap.find(name);
-
- if(iter != mChannelMap.end())
+ if(mAudioSession)
{
- result = iter->second;
+ result = mAudioSession->findParticipantByID(id);
}
return result;
}
+
void LLVoiceClient::parcelChanged()
{
- if(getState() >= stateLoggedIn)
+ if(getState() >= stateNoChannel)
{
// If the user is logged in, start a channel lookup.
LL_DEBUGS("Voice") << "sending ParcelVoiceInfoRequest (" << mCurrentRegionName << ", " << mCurrentParcelLocalID << ")" << LL_ENDL;
@@ -3243,7 +4964,7 @@ void LLVoiceClient::parcelChanged()
}
else
{
- // The transition to stateLoggedIn needs to kick this off again.
+ // The transition to stateNoChannel needs to kick this off again.
LL_INFOS("Voice") << "not logged in yet, deferring" << LL_ENDL;
}
}
@@ -3251,12 +4972,17 @@ void LLVoiceClient::parcelChanged()
void LLVoiceClient::switchChannel(
std::string uri,
bool spatial,
- bool noReconnect,
+ bool no_reconnect,
+ bool is_p2p,
std::string hash)
{
bool needsSwitch = false;
- LL_DEBUGS("Voice") << "called in state " << state2string(getState()) << " with uri \"" << uri << "\"" << LL_ENDL;
+ LL_DEBUGS("Voice")
+ << "called in state " << state2string(getState())
+ << " with uri \"" << uri << "\""
+ << (spatial?", spatial is true":", spatial is false")
+ << LL_ENDL;
switch(getState())
{
@@ -3266,18 +4992,18 @@ void LLVoiceClient::switchChannel(
// Always switch to the new URI from these states.
needsSwitch = true;
break;
-
+
default:
if(mSessionTerminateRequested)
{
// If a terminate has been requested, we need to compare against where the URI we're already headed to.
- if(mNextSessionURI != uri)
+ if(mNextAudioSession && (mNextAudioSession->mSIPURI != uri))
needsSwitch = true;
}
else
{
// Otherwise, compare against the URI we're in now.
- if(mSessionURI != uri)
+ if(mAudioSession && (mAudioSession->mSIPURI != uri))
needsSwitch = true;
}
break;
@@ -3285,22 +5011,28 @@ void LLVoiceClient::switchChannel(
if(needsSwitch)
{
- mNextSessionURI = uri;
- mNextSessionHash = hash;
- mNextSessionHandle.clear();
- mNextP2PSessionURI.clear();
- mNextSessionSpatial = spatial;
- mNextSessionNoReconnect = noReconnect;
-
if(uri.empty())
{
// Leave any channel we may be in
LL_DEBUGS("Voice") << "leaving channel" << LL_ENDL;
+
+ sessionState *oldSession = mNextAudioSession;
+ mNextAudioSession = NULL;
+
+ // The old session may now need to be deleted.
+ reapSession(oldSession);
+
notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_VOICE_DISABLED);
}
else
{
LL_DEBUGS("Voice") << "switching to channel " << uri << LL_ENDL;
+
+ mNextAudioSession = addSession(uri);
+ mNextAudioSession->mHash = hash;
+ mNextAudioSession->mIsSpatial = spatial;
+ mNextAudioSession->mReconnect = !no_reconnect;
+ mNextAudioSession->mIsP2P = is_p2p;
}
if(getState() <= stateNoChannel)
@@ -3315,15 +5047,10 @@ void LLVoiceClient::switchChannel(
}
}
-void LLVoiceClient::joinSession(std::string handle, std::string uri)
+void LLVoiceClient::joinSession(sessionState *session)
{
- mNextSessionURI.clear();
- mNextSessionHash.clear();
- mNextP2PSessionURI = uri;
- mNextSessionHandle = handle;
- mNextSessionSpatial = false;
- mNextSessionNoReconnect = false;
-
+ mNextAudioSession = session;
+
if(getState() <= stateNoChannel)
{
// We're already set up to join a channel, just needed to fill in the session handle
@@ -3339,7 +5066,7 @@ void LLVoiceClient::setNonSpatialChannel(
const std::string &uri,
const std::string &credentials)
{
- switchChannel(uri, false, false, credentials);
+ switchChannel(uri, false, false, false, credentials);
}
void LLVoiceClient::setSpatialChannel(
@@ -3347,51 +5074,216 @@ void LLVoiceClient::setSpatialChannel(
const std::string &credentials)
{
mSpatialSessionURI = uri;
+ mSpatialSessionCredentials = credentials;
mAreaVoiceDisabled = mSpatialSessionURI.empty();
LL_DEBUGS("Voice") << "got spatial channel uri: \"" << uri << "\"" << LL_ENDL;
- if(mNonSpatialChannel || !mNextSessionSpatial)
+ if((mAudioSession && !(mAudioSession->mIsSpatial)) || (mNextAudioSession && !(mNextAudioSession->mIsSpatial)))
{
// User is in a non-spatial chat or joining a non-spatial chat. Don't switch channels.
LL_INFOS("Voice") << "in non-spatial chat, not switching channels" << LL_ENDL;
}
else
{
- switchChannel(mSpatialSessionURI, true, false, credentials);
+ switchChannel(mSpatialSessionURI, true, false, false, mSpatialSessionCredentials);
}
}
-void LLVoiceClient::callUser(LLUUID &uuid)
+void LLVoiceClient::callUser(const LLUUID &uuid)
{
std::string userURI = sipURIFromID(uuid);
- switchChannel(userURI, false, true);
+ switchChannel(userURI, false, true, true);
+}
+
+LLVoiceClient::sessionState* LLVoiceClient::startUserIMSession(const LLUUID &uuid)
+{
+ // Figure out if a session with the user already exists
+ sessionState *session = findSession(uuid);
+ if(!session)
+ {
+ // No session with user, need to start one.
+ std::string uri = sipURIFromID(uuid);
+ session = addSession(uri);
+ session->mIsSpatial = false;
+ session->mReconnect = false;
+ session->mIsP2P = true;
+ session->mCallerID = uuid;
+ }
+
+ if(session)
+ {
+ if(session->mHandle.empty())
+ {
+ // Session isn't active -- start it up.
+ sessionCreateSendMessage(session, false, true);
+ }
+ else
+ {
+ // Session is already active -- start up text.
+ sessionTextConnectSendMessage(session);
+ }
+ }
+
+ return session;
+}
+
+bool LLVoiceClient::sendTextMessage(const LLUUID& participant_id, const std::string& message)
+{
+ bool result = false;
+
+ // Attempt to locate the indicated session
+ sessionState *session = startUserIMSession(participant_id);
+ if(session)
+ {
+ // found the session, attempt to send the message
+ session->mTextMsgQueue.push(message);
+
+ // Try to send queued messages (will do nothing if the session is not open yet)
+ sendQueuedTextMessages(session);
+
+ // The message is queued, so we succeed.
+ result = true;
+ }
+ else
+ {
+ LL_DEBUGS("Voice") << "Session not found for participant ID " << participant_id << LL_ENDL;
+ }
+
+ return result;
+}
+
+void LLVoiceClient::sendQueuedTextMessages(sessionState *session)
+{
+ if(session->mTextStreamState == 1)
+ {
+ if(!session->mTextMsgQueue.empty())
+ {
+ std::ostringstream stream;
+
+ while(!session->mTextMsgQueue.empty())
+ {
+ std::string message = session->mTextMsgQueue.front();
+ session->mTextMsgQueue.pop();
+ stream
+ << ""
+ << "" << session->mHandle << ""
+ << "text/HTML"
+ << "" << message << ""
+ << ""
+ << "\n\n\n";
+ }
+ writeString(stream.str());
+ }
+ }
+ else
+ {
+ // Session isn't connected yet, defer until later.
+ }
+}
+
+void LLVoiceClient::endUserIMSession(const LLUUID &uuid)
+{
+ // Figure out if a session with the user exists
+ sessionState *session = findSession(uuid);
+ if(session)
+ {
+ // found the session
+ if(!session->mHandle.empty())
+ {
+ sessionTextDisconnectSendMessage(session);
+ }
+ }
+ else
+ {
+ LL_DEBUGS("Voice") << "Session not found for participant ID " << uuid << LL_ENDL;
+ }
+}
+
+bool LLVoiceClient::answerInvite(std::string &sessionHandle)
+{
+ // this is only ever used to answer incoming p2p call invites.
+
+ sessionState *session = findSession(sessionHandle);
+ if(session)
+ {
+ session->mIsSpatial = false;
+ session->mReconnect = false;
+ session->mIsP2P = true;
+
+ joinSession(session);
+ return true;
+ }
+
+ return false;
}
-void LLVoiceClient::answerInvite(std::string &sessionHandle, LLUUID& other_user_id)
+bool LLVoiceClient::isOnlineSIP(const LLUUID &id)
{
- joinSession(sessionHandle, sipURIFromID(other_user_id));
+ bool result = false;
+ buddyListEntry *buddy = findBuddy(id);
+ if(buddy)
+ {
+ result = buddy->mOnlineSLim;
+ LL_DEBUGS("Voice") << "Buddy " << buddy->mDisplayName << " is SIP " << (result?"online":"offline") << LL_ENDL;
+ }
+
+ if(!result)
+ {
+ // This user isn't on the buddy list or doesn't show online status through the buddy list, but could be a participant in an existing session if they initiated a text IM.
+ sessionState *session = findSession(id);
+ if(session && !session->mHandle.empty())
+ {
+ if((session->mTextStreamState != streamStateUnknown) || (session->mMediaStreamState > streamStateIdle))
+ {
+ LL_DEBUGS("Voice") << "Open session with " << id << " found, returning SIP online state" << LL_ENDL;
+ // we have a p2p text session open with this user, so by definition they're online.
+ result = true;
+ }
+ }
+ }
+
+ return result;
}
void LLVoiceClient::declineInvite(std::string &sessionHandle)
{
- sessionTerminateByHandle(sessionHandle);
+ sessionState *session = findSession(sessionHandle);
+ if(session)
+ {
+ sessionMediaDisconnectSendMessage(session);
+ }
}
void LLVoiceClient::leaveNonSpatialChannel()
{
- switchChannel(mSpatialSessionURI);
+ LL_DEBUGS("Voice")
+ << "called in state " << state2string(getState())
+ << LL_ENDL;
+
+ // Make sure we don't rejoin the current session.
+ sessionState *oldNextSession = mNextAudioSession;
+ mNextAudioSession = NULL;
+
+ // Most likely this will still be the current session at this point, but check it anyway.
+ reapSession(oldNextSession);
+
+ verifySessionState();
+
+ sessionTerminate();
}
std::string LLVoiceClient::getCurrentChannel()
{
+ std::string result;
+
if((getState() == stateRunning) && !mSessionTerminateRequested)
{
- return mSessionURI;
+ result = getAudioSessionURI();
}
- return "";
+ return result;
}
bool LLVoiceClient::inProximalChannel()
@@ -3400,7 +5292,7 @@ bool LLVoiceClient::inProximalChannel()
if((getState() == stateRunning) && !mSessionTerminateRequested)
{
- result = !mNonSpatialChannel;
+ result = inSpatialChannel();
}
return result;
@@ -3412,7 +5304,7 @@ std::string LLVoiceClient::sipURIFromID(const LLUUID &id)
result = "sip:";
result += nameFromID(id);
result += "@";
- result += mAccountServerName;
+ result += mVoiceSIPURIHostName;
return result;
}
@@ -3425,7 +5317,7 @@ std::string LLVoiceClient::sipURIFromAvatar(LLVOAvatar *avatar)
result = "sip:";
result += nameFromID(avatar->getID());
result += "@";
- result += mAccountServerName;
+ result += mVoiceSIPURIHostName;
}
return result;
@@ -3444,6 +5336,13 @@ std::string LLVoiceClient::nameFromAvatar(LLVOAvatar *avatar)
std::string LLVoiceClient::nameFromID(const LLUUID &uuid)
{
std::string result;
+
+ if (uuid.isNull()) {
+ //VIVOX, the uuid emtpy look for the mURIString and return that instead.
+ //result.assign(uuid.mURIStringName);
+ LLStringUtil::replaceChar(result, '_', ' ');
+ return result;
+ }
// Prepending this apparently prevents conflicts with reserved names inside the vivox and diamondware code.
result = "x";
@@ -3457,13 +5356,24 @@ std::string LLVoiceClient::nameFromID(const LLUUID &uuid)
// If you need to transform a GUID to this form on the Mac OS X command line, this will do so:
// echo -n x && (echo e669132a-6c43-4ee1-a78d-6c82fff59f32 |xxd -r -p |openssl base64|tr '/+' '_-')
+ // The reverse transform can be done with:
+ // echo 'x5mkTKmxDTuGnjWyC__WfMg==' |cut -b 2- -|tr '_-' '/+' |openssl base64 -d|xxd -p
+
return result;
}
-bool LLVoiceClient::IDFromName(const std::string name, LLUUID &uuid)
+bool LLVoiceClient::IDFromName(const std::string inName, LLUUID &uuid)
{
bool result = false;
+ // SLIM SDK: The "name" may actually be a SIP URI such as: "sip:xFnPP04IpREWNkuw1cOXlhw==@bhr.vivox.com"
+ // If it is, convert to a bare name before doing the transform.
+ std::string name = nameFromsipURI(inName);
+
+ // Doesn't look like a SIP URI, assume it's an actual name.
+ if(name.empty())
+ name = inName;
+
// This will only work if the name is of the proper form.
// As an example, the account name for Monroe Linden (UUID 1673cfd3-8229-4445-8d92-ec3570e5e587) is:
// "xFnPP04IpREWNkuw1cOXlhw=="
@@ -3485,6 +5395,13 @@ bool LLVoiceClient::IDFromName(const std::string name, LLUUID &uuid)
memcpy(uuid.mData, rawuuid, UUID_BYTES);
result = true;
}
+ }
+
+ if(!result)
+ {
+ // VIVOX: not a standard account name, just copy the URI name mURIString field
+ // and hope for the best. bpj
+ uuid.setNull(); // VIVOX, set the uuid field to nulls
}
return result;
@@ -3501,13 +5418,59 @@ std::string LLVoiceClient::sipURIFromName(std::string &name)
result = "sip:";
result += name;
result += "@";
- result += mAccountServerName;
+ result += mVoiceSIPURIHostName;
// LLStringUtil::toLower(result);
return result;
}
+std::string LLVoiceClient::nameFromsipURI(const std::string &uri)
+{
+ std::string result;
+
+ std::string::size_type sipOffset, atOffset;
+ sipOffset = uri.find("sip:");
+ atOffset = uri.find("@");
+ if((sipOffset != std::string::npos) && (atOffset != std::string::npos))
+ {
+ result = uri.substr(sipOffset + 4, atOffset - (sipOffset + 4));
+ }
+
+ return result;
+}
+
+bool LLVoiceClient::inSpatialChannel(void)
+{
+ bool result = false;
+
+ if(mAudioSession)
+ result = mAudioSession->mIsSpatial;
+
+ return result;
+}
+
+std::string LLVoiceClient::getAudioSessionURI()
+{
+ std::string result;
+
+ if(mAudioSession)
+ result = mAudioSession->mSIPURI;
+
+ return result;
+}
+
+std::string LLVoiceClient::getAudioSessionHandle()
+{
+ std::string result;
+
+ if(mAudioSession)
+ result = mAudioSession->mHandle;
+
+ return result;
+}
+
+
/////////////////////////////
// Sending updates of current state
@@ -3546,7 +5509,8 @@ void LLVoiceClient::updatePosition(void)
LLMatrix3 rot;
LLVector3d pos;
- // MBW -- XXX -- Setting both camera and avatar velocity to 0 for now. May figure it out later...
+ // TODO: If camera and avatar velocity are actually used by the voice system, we could compute them here...
+ // They're currently always set to zero.
// Send the current camera position to the voice code
rot.setRows(LLViewerCamera::getInstance()->getAtAxis(), LLViewerCamera::getInstance()->getLeftAxis (), LLViewerCamera::getInstance()->getUpAxis());
@@ -3561,7 +5525,7 @@ void LLVoiceClient::updatePosition(void)
rot = agent->getRootJoint()->getWorldRotation().getMatrix3();
pos = agent->getPositionGlobal();
- // MBW -- XXX -- Can we get the head offset from outside the LLVOAvatar?
+ // TODO: Can we get the head offset from outside the LLVOAvatar?
// pos += LLVector3d(mHeadOffset);
pos += LLVector3d(0.f, 0.f, 1.f);
@@ -3748,375 +5712,1037 @@ void LLVoiceClient::setEarLocation(S32 loc)
void LLVoiceClient::setVoiceVolume(F32 volume)
{
- LL_DEBUGS("Voice") << "volume is " << volume << LL_ENDL;
+ int scaled_volume = scale_speaker_volume(volume);
- // incoming volume has the range [0.0 ... 1.0], with 0.5 as the default.
- // Map it as follows: 0.0 -> -100, 0.5 -> 24, 1.0 -> 50
+ if(scaled_volume != mSpeakerVolume)
+ {
+ if((scaled_volume == 0) || (mSpeakerVolume == 0))
+ {
+ mSpeakerMuteDirty = true;
+ }
+
+ mSpeakerVolume = scaled_volume;
+ mSpeakerVolumeDirty = true;
+ }
+}
+
+void LLVoiceClient::setMicGain(F32 volume)
+{
+ int scaled_volume = scale_mic_volume(volume);
- volume -= 0.5f; // offset volume to the range [-0.5 ... 0.5], with 0 at the default.
- int scaledVolume = 24; // offset scaledVolume by its default level
- if(volume < 0.0f)
- scaledVolume += ((int)(volume * 248.0f)); // (24 - (-100)) * 2
+ if(scaled_volume != mMicVolume)
+ {
+ mMicVolume = scaled_volume;
+ mMicVolumeDirty = true;
+ }
+}
+
+void LLVoiceClient::keyDown(KEY key, MASK mask)
+{
+// LL_DEBUGS("Voice") << "key is " << LLKeyboard::stringFromKey(key) << LL_ENDL;
+
+ if (gKeyboard->getKeyRepeated(key))
+ {
+ // ignore auto-repeat keys
+ return;
+ }
+
+ if(!mPTTIsMiddleMouse)
+ {
+ if(mPTTIsToggle)
+ {
+ if(key == mPTTKey)
+ {
+ toggleUserPTTState();
+ }
+ }
+ else if(mPTTKey != KEY_NONE)
+ {
+ setUserPTTState(gKeyboard->getKeyDown(mPTTKey));
+ }
+ }
+}
+void LLVoiceClient::keyUp(KEY key, MASK mask)
+{
+ if(!mPTTIsMiddleMouse)
+ {
+ if(!mPTTIsToggle && (mPTTKey != KEY_NONE))
+ {
+ setUserPTTState(gKeyboard->getKeyDown(mPTTKey));
+ }
+ }
+}
+void LLVoiceClient::middleMouseState(bool down)
+{
+ if(mPTTIsMiddleMouse)
+ {
+ if(mPTTIsToggle)
+ {
+ if(down)
+ {
+ toggleUserPTTState();
+ }
+ }
+ else
+ {
+ setUserPTTState(down);
+ }
+ }
+}
+
+/////////////////////////////
+// Accessors for data related to nearby speakers
+BOOL LLVoiceClient::getVoiceEnabled(const LLUUID& id)
+{
+ BOOL result = FALSE;
+ participantState *participant = findParticipantByID(id);
+ if(participant)
+ {
+ // I'm not sure what the semantics of this should be.
+ // For now, if we have any data about the user that came through the chat channel, assume they're voice-enabled.
+ result = TRUE;
+ }
+
+ return result;
+}
+
+BOOL LLVoiceClient::getIsSpeaking(const LLUUID& id)
+{
+ BOOL result = FALSE;
+
+ participantState *participant = findParticipantByID(id);
+ if(participant)
+ {
+ if (participant->mSpeakingTimeout.getElapsedTimeF32() > SPEAKING_TIMEOUT)
+ {
+ participant->mIsSpeaking = FALSE;
+ }
+ result = participant->mIsSpeaking;
+ }
+
+ return result;
+}
+
+BOOL LLVoiceClient::getIsModeratorMuted(const LLUUID& id)
+{
+ BOOL result = FALSE;
+
+ participantState *participant = findParticipantByID(id);
+ if(participant)
+ {
+ result = participant->mIsModeratorMuted;
+ }
+
+ return result;
+}
+
+F32 LLVoiceClient::getCurrentPower(const LLUUID& id)
+{
+ F32 result = 0;
+ participantState *participant = findParticipantByID(id);
+ if(participant)
+ {
+ result = participant->mPower;
+ }
+
+ return result;
+}
+
+
+std::string LLVoiceClient::getDisplayName(const LLUUID& id)
+{
+ std::string result;
+ participantState *participant = findParticipantByID(id);
+ if(participant)
+ {
+ result = participant->mDisplayName;
+ }
+
+ return result;
+}
+
+
+BOOL LLVoiceClient::getUsingPTT(const LLUUID& id)
+{
+ BOOL result = FALSE;
+
+ participantState *participant = findParticipantByID(id);
+ if(participant)
+ {
+ // I'm not sure what the semantics of this should be.
+ // Does "using PTT" mean they're configured with a push-to-talk button?
+ // For now, we know there's no PTT mechanism in place, so nobody is using it.
+ }
+
+ return result;
+}
+
+BOOL LLVoiceClient::getOnMuteList(const LLUUID& id)
+{
+ BOOL result = FALSE;
+
+ participantState *participant = findParticipantByID(id);
+ if(participant)
+ {
+ result = participant->mOnMuteList;
+ }
+
+ return result;
+}
+
+// External accessiors. Maps 0.0 to 1.0 to internal values 0-400 with .5 == 100
+// internal = 400 * external^2
+F32 LLVoiceClient::getUserVolume(const LLUUID& id)
+{
+ F32 result = 0.0f;
+
+ participantState *participant = findParticipantByID(id);
+ if(participant)
+ {
+ S32 ires = participant->mUserVolume; // 0-400
+ result = sqrtf(((F32)ires) / 400.f);
+ }
+
+ return result;
+}
+
+void LLVoiceClient::setUserVolume(const LLUUID& id, F32 volume)
+{
+ if(mAudioSession)
+ {
+ participantState *participant = findParticipantByID(id);
+ if (participant)
+ {
+ // volume can amplify by as much as 4x!
+ S32 ivol = (S32)(400.f * volume * volume);
+ participant->mUserVolume = llclamp(ivol, 0, 400);
+ participant->mVolumeDirty = TRUE;
+ mAudioSession->mVolumeDirty = TRUE;
+ }
+ }
+}
+
+std::string LLVoiceClient::getGroupID(const LLUUID& id)
+{
+ std::string result;
+
+ participantState *participant = findParticipantByID(id);
+ if(participant)
+ {
+ result = participant->mGroupID;
+ }
+
+ return result;
+}
+
+BOOL LLVoiceClient::getAreaVoiceDisabled()
+{
+ return mAreaVoiceDisabled;
+}
+
+void LLVoiceClient::recordingLoopStart(int seconds, int deltaFramesPerControlFrame)
+{
+// LL_DEBUGS("Voice") << "sending SessionGroup.ControlRecording (Start)" << LL_ENDL;
+
+ if(!mMainSessionGroupHandle.empty())
+ {
+ std::ostringstream stream;
+ stream
+ << ""
+ << "" << mMainSessionGroupHandle << ""
+ << "Start"
+ << "" << deltaFramesPerControlFrame << ""
+ << "" << "" << ""
+ << "false"
+ << "" << seconds << ""
+ << "\n\n\n";
+
+
+ writeString(stream.str());
+ }
+}
+
+void LLVoiceClient::recordingLoopSave(const std::string& filename)
+{
+// LL_DEBUGS("Voice") << "sending SessionGroup.ControlRecording (Flush)" << LL_ENDL;
+
+ if(mAudioSession != NULL && !mAudioSession->mGroupHandle.empty())
+ {
+ std::ostringstream stream;
+ stream
+ << ""
+ << "" << mMainSessionGroupHandle << ""
+ << "Flush"
+ << "" << filename << ""
+ << "\n\n\n";
+
+ writeString(stream.str());
+ }
+}
+
+void LLVoiceClient::recordingStop()
+{
+// LL_DEBUGS("Voice") << "sending SessionGroup.ControlRecording (Stop)" << LL_ENDL;
+
+ if(mAudioSession != NULL && !mAudioSession->mGroupHandle.empty())
+ {
+ std::ostringstream stream;
+ stream
+ << ""
+ << "" << mMainSessionGroupHandle << ""
+ << "Stop"
+ << "\n\n\n";
+
+ writeString(stream.str());
+ }
+}
+
+void LLVoiceClient::filePlaybackStart(const std::string& filename)
+{
+// LL_DEBUGS("Voice") << "sending SessionGroup.ControlPlayback (Start)" << LL_ENDL;
+
+ if(mAudioSession != NULL && !mAudioSession->mGroupHandle.empty())
+ {
+ std::ostringstream stream;
+ stream
+ << ""
+ << "" << mMainSessionGroupHandle << ""
+ << "Start"
+ << "" << filename << ""
+ << "\n\n\n";
+
+ writeString(stream.str());
+ }
+}
+
+void LLVoiceClient::filePlaybackStop()
+{
+// LL_DEBUGS("Voice") << "sending SessionGroup.ControlPlayback (Stop)" << LL_ENDL;
+
+ if(mAudioSession != NULL && !mAudioSession->mGroupHandle.empty())
+ {
+ std::ostringstream stream;
+ stream
+ << ""
+ << "" << mMainSessionGroupHandle << ""
+ << "Stop"
+ << "\n\n\n";
+
+ writeString(stream.str());
+ }
+}
+
+void LLVoiceClient::filePlaybackSetPaused(bool paused)
+{
+ // TODO: Implement once Vivox gives me a sample
+}
+
+void LLVoiceClient::filePlaybackSetMode(bool vox, float speed)
+{
+ // TODO: Implement once Vivox gives me a sample
+}
+
+LLVoiceClient::sessionState::sessionState() :
+ mMediaStreamState(streamStateUnknown),
+ mTextStreamState(streamStateUnknown),
+ mCreateInProgress(false),
+ mMediaConnectInProgress(false),
+ mVoiceInvitePending(false),
+ mTextInvitePending(false),
+ mSynthesizedCallerID(false),
+ mIsChannel(false),
+ mIsSpatial(false),
+ mIsP2P(false),
+ mIncoming(false),
+ mVoiceEnabled(false),
+ mReconnect(false),
+ mVolumeDirty(false),
+ mParticipantsChanged(false)
+{
+}
+
+LLVoiceClient::sessionState::~sessionState()
+{
+ removeAllParticipants();
+}
+
+LLVoiceClient::sessionIterator LLVoiceClient::sessionsBegin(void)
+{
+ return mSessions.begin();
+}
+
+LLVoiceClient::sessionIterator LLVoiceClient::sessionsEnd(void)
+{
+ return mSessions.end();
+}
+
+
+LLVoiceClient::sessionState *LLVoiceClient::findSession(const std::string &handle)
+{
+ sessionState *result = NULL;
+ sessionMap::iterator iter = mSessionsByHandle.find(&handle);
+ if(iter != mSessionsByHandle.end())
+ {
+ result = iter->second;
+ }
+
+ return result;
+}
+
+LLVoiceClient::sessionState *LLVoiceClient::findSessionBeingCreatedByURI(const std::string &uri)
+{
+ sessionState *result = NULL;
+ for(sessionIterator iter = sessionsBegin(); iter != sessionsEnd(); iter++)
+ {
+ sessionState *session = *iter;
+ if(session->mCreateInProgress && (session->mSIPURI == uri))
+ {
+ result = session;
+ break;
+ }
+ }
+
+ return result;
+}
+
+LLVoiceClient::sessionState *LLVoiceClient::findSession(const LLUUID &participant_id)
+{
+ sessionState *result = NULL;
+
+ for(sessionIterator iter = sessionsBegin(); iter != sessionsEnd(); iter++)
+ {
+ sessionState *session = *iter;
+ if(session->mCallerID == participant_id)
+ {
+ result = session;
+ break;
+ }
+ }
+
+ return result;
+}
+
+LLVoiceClient::sessionState *LLVoiceClient::addSession(const std::string &uri, const std::string &handle)
+{
+ sessionState *result = NULL;
+
+ if(handle.empty())
+ {
+ // No handle supplied.
+ // Check whether there's already a session with this URI
+ for(sessionIterator iter = sessionsBegin(); iter != sessionsEnd(); iter++)
+ {
+ sessionState *s = *iter;
+ if((s->mSIPURI == uri) || (s->mAlternateSIPURI == uri))
+ {
+ // TODO: I need to think about this logic... it's possible that this case should raise an internal error.
+ result = s;
+ break;
+ }
+ }
+ }
+ else // (!handle.empty())
+ {
+ // Check for an existing session with this handle
+ sessionMap::iterator iter = mSessionsByHandle.find(&handle);
+
+ if(iter != mSessionsByHandle.end())
+ {
+ result = iter->second;
+ }
+ }
+
+ if(!result)
+ {
+ // No existing session found.
+
+ LL_DEBUGS("Voice") << "adding new session: handle " << handle << " URI " << uri << LL_ENDL;
+ result = new sessionState();
+ result->mSIPURI = uri;
+ result->mHandle = handle;
+
+ mSessions.insert(result);
+
+ if(!result->mHandle.empty())
+ {
+ mSessionsByHandle.insert(sessionMap::value_type(&(result->mHandle), result));
+ }
+ }
else
- scaledVolume += ((int)(volume * 52.0f)); // (50 - 24) * 2
+ {
+ // Found an existing session
+
+ if(uri != result->mSIPURI)
+ {
+ // TODO: Should this be an internal error?
+ LL_DEBUGS("Voice") << "changing uri from " << result->mSIPURI << " to " << uri << LL_ENDL;
+ setSessionURI(result, uri);
+ }
+
+ if(handle != result->mHandle)
+ {
+ // TODO: Should this be an internal error?
+ LL_DEBUGS("Voice") << "changing handle from " << result->mHandle << " to " << handle << LL_ENDL;
+ setSessionHandle(result, handle);
+ }
+
+ LL_DEBUGS("Voice") << "returning existing session: handle " << handle << " URI " << uri << LL_ENDL;
+ }
+
+ verifySessionState();
+
+ return result;
+}
+
+void LLVoiceClient::setSessionHandle(sessionState *session, const std::string &handle)
+{
+ // Have to remove the session from the handle-indexed map before changing the handle, or things will break badly.
- if(scaledVolume != mSpeakerVolume)
+ if(!session->mHandle.empty())
{
- if((scaledVolume == -100) || (mSpeakerVolume == -100))
+ // Remove session from the map if it should have been there.
+ sessionMap::iterator iter = mSessionsByHandle.find(&(session->mHandle));
+ if(iter != mSessionsByHandle.end())
{
- mSpeakerMuteDirty = true;
+ if(iter->second != session)
+ {
+ LL_ERRS("Voice") << "Internal error: session mismatch!" << LL_ENDL;
+ }
+
+ mSessionsByHandle.erase(iter);
+ }
+ else
+ {
+ LL_ERRS("Voice") << "Internal error: session handle not found in map!" << LL_ENDL;
}
+ }
+
+ session->mHandle = handle;
- mSpeakerVolume = scaledVolume;
- mSpeakerVolumeDirty = true;
+ if(!handle.empty())
+ {
+ mSessionsByHandle.insert(sessionMap::value_type(&(session->mHandle), session));
}
+
+ verifySessionState();
}
-void LLVoiceClient::setMicGain(F32 volume)
+void LLVoiceClient::setSessionURI(sessionState *session, const std::string &uri)
{
- int scaledVolume = ((int)(volume * 100.0f)) - 100;
- if(scaledVolume != mMicVolume)
- {
- mMicVolume = scaledVolume;
- mMicVolumeDirty = true;
- }
+ // There used to be a map of session URIs to sessions, which made this complex....
+ session->mSIPURI = uri;
+
+ verifySessionState();
}
-void LLVoiceClient::setVivoxDebugServerName(std::string &serverName)
+void LLVoiceClient::deleteSession(sessionState *session)
{
- if(!mAccountServerName.empty())
+ // Remove the session from the handle map
+ if(!session->mHandle.empty())
{
- // The name has been filled in already, which means we know whether we're connecting to agni or not.
- if(!sConnectingToAgni)
+ sessionMap::iterator iter = mSessionsByHandle.find(&(session->mHandle));
+ if(iter != mSessionsByHandle.end())
{
- // Only use the setting if we're connecting to a development grid -- always use bhr when on agni.
- mAccountServerName = serverName;
+ if(iter->second != session)
+ {
+ LL_ERRS("Voice") << "Internal error: session mismatch" << LL_ENDL
+ }
+ mSessionsByHandle.erase(iter);
}
}
-}
-void LLVoiceClient::keyDown(KEY key, MASK mask)
-{
- LL_DEBUGS("Voice") << "key is " << LLKeyboard::stringFromKey(key) << LL_ENDL;
+ // Remove the session from the URI map
+ mSessions.erase(session);
+
+ // At this point, the session should be unhooked from all lists and all state should be consistent.
+ verifySessionState();
- if (gKeyboard->getKeyRepeated(key))
+ // If this is the current audio session, clean up the pointer which will soon be dangling.
+ if(mAudioSession == session)
{
- // ignore auto-repeat keys
- return;
+ mAudioSession = NULL;
+ mAudioSessionChanged = true;
}
- if(!mPTTIsMiddleMouse)
+ // ditto for the next audio session
+ if(mNextAudioSession == session)
{
- if(mPTTIsToggle)
- {
- if(key == mPTTKey)
- {
- toggleUserPTTState();
- }
- }
- else if(mPTTKey != KEY_NONE)
- {
- setUserPTTState(gKeyboard->getKeyDown(mPTTKey));
- }
+ mNextAudioSession = NULL;
}
+
+ // delete the session
+ delete session;
}
-void LLVoiceClient::keyUp(KEY key, MASK mask)
+
+void LLVoiceClient::deleteAllSessions()
{
- if(!mPTTIsMiddleMouse)
+ LL_DEBUGS("Voice") << "called" << LL_ENDL;
+
+ while(!mSessions.empty())
{
- if(!mPTTIsToggle && (mPTTKey != KEY_NONE))
- {
- setUserPTTState(gKeyboard->getKeyDown(mPTTKey));
- }
+ deleteSession(*(sessionsBegin()));
+ }
+
+ if(!mSessionsByHandle.empty())
+ {
+ LL_ERRS("Voice") << "Internal error: empty session map, non-empty handle map" << LL_ENDL
}
}
-void LLVoiceClient::middleMouseState(bool down)
+
+void LLVoiceClient::verifySessionState(void)
{
- if(mPTTIsMiddleMouse)
+ // This is mostly intended for debugging problems with session state management.
+ LL_DEBUGS("Voice") << "Total session count: " << mSessions.size() << " , session handle map size: " << mSessionsByHandle.size() << LL_ENDL;
+
+ for(sessionIterator iter = sessionsBegin(); iter != sessionsEnd(); iter++)
{
- if(mPTTIsToggle)
+ sessionState *session = *iter;
+
+ LL_DEBUGS("Voice") << "session " << session << ": handle " << session->mHandle << ", URI " << session->mSIPURI << LL_ENDL;
+
+ if(!session->mHandle.empty())
{
- if(down)
+ // every session with a non-empty handle needs to be in the handle map
+ sessionMap::iterator i2 = mSessionsByHandle.find(&(session->mHandle));
+ if(i2 == mSessionsByHandle.end())
{
- toggleUserPTTState();
+ LL_ERRS("Voice") << "internal error (handle " << session->mHandle << " not found in session map)" << LL_ENDL;
+ }
+ else
+ {
+ if(i2->second != session)
+ {
+ LL_ERRS("Voice") << "internal error (handle " << session->mHandle << " in session map points to another session)" << LL_ENDL;
+ }
}
}
+ }
+
+ // check that every entry in the handle map points to a valid session in the session set
+ for(sessionMap::iterator iter = mSessionsByHandle.begin(); iter != mSessionsByHandle.end(); iter++)
+ {
+ sessionState *session = iter->second;
+ sessionIterator i2 = mSessions.find(session);
+ if(i2 == mSessions.end())
+ {
+ LL_ERRS("Voice") << "internal error (session for handle " << session->mHandle << " not found in session map)" << LL_ENDL;
+ }
else
{
- setUserPTTState(down);
+ if(session->mHandle != (*i2)->mHandle)
+ {
+ LL_ERRS("Voice") << "internal error (session for handle " << session->mHandle << " points to session with different handle " << (*i2)->mHandle << ")" << LL_ENDL;
+ }
}
}
}
-/////////////////////////////
-// Accessors for data related to nearby speakers
-BOOL LLVoiceClient::getVoiceEnabled(const LLUUID& id)
+LLVoiceClient::buddyListEntry::buddyListEntry(const std::string &uri) :
+ mURI(uri)
{
- BOOL result = FALSE;
- participantState *participant = findParticipantByID(id);
- if(participant)
- {
- // I'm not sure what the semantics of this should be.
- // For now, if we have any data about the user that came through the chat channel, assume they're voice-enabled.
- result = TRUE;
- }
-
- return result;
+ mOnlineSL = false;
+ mOnlineSLim = false;
+ mCanSeeMeOnline = true;
+ mHasBlockListEntry = false;
+ mHasAutoAcceptListEntry = false;
+ mNameResolved = false;
+ mInVivoxBuddies = false;
+ mInSLFriends = false;
+ mNeedsNameUpdate = false;
}
-BOOL LLVoiceClient::getIsSpeaking(const LLUUID& id)
+void LLVoiceClient::processBuddyListEntry(const std::string &uri, const std::string &displayName)
{
- BOOL result = FALSE;
+ buddyListEntry *buddy = addBuddy(uri, displayName);
+ buddy->mInVivoxBuddies = true;
+}
- participantState *participant = findParticipantByID(id);
- if(participant)
+LLVoiceClient::buddyListEntry *LLVoiceClient::addBuddy(const std::string &uri)
+{
+ std::string empty;
+ buddyListEntry *buddy = addBuddy(uri, empty);
+ if(buddy->mDisplayName.empty())
{
- if (participant->mSpeakingTimeout.getElapsedTimeF32() > SPEAKING_TIMEOUT)
- {
- participant->mIsSpeaking = FALSE;
- }
- result = participant->mIsSpeaking;
+ buddy->mNameResolved = false;
}
-
- return result;
+ return buddy;
}
-BOOL LLVoiceClient::getIsModeratorMuted(const LLUUID& id)
+LLVoiceClient::buddyListEntry *LLVoiceClient::addBuddy(const std::string &uri, const std::string &displayName)
{
- BOOL result = FALSE;
+ buddyListEntry *result = NULL;
+ buddyListMap::iterator iter = mBuddyListMap.find(&uri);
+
+ if(iter != mBuddyListMap.end())
+ {
+ // Found a matching buddy already in the map.
+ LL_DEBUGS("Voice") << "adding existing buddy " << uri << LL_ENDL;
+ result = iter->second;
+ }
- participantState *participant = findParticipantByID(id);
- if(participant)
+ if(!result)
{
- result = participant->mIsModeratorMuted;
+ // participant isn't already in one list or the other.
+ LL_DEBUGS("Voice") << "adding new buddy " << uri << LL_ENDL;
+ result = new buddyListEntry(uri);
+ result->mDisplayName = displayName;
+
+ if(IDFromName(uri, result->mUUID))
+ {
+ // Extracted UUID from name successfully.
+ }
+ else
+ {
+ LL_DEBUGS("Voice") << "Couldn't find ID for buddy " << uri << " (\"" << displayName << "\")" << LL_ENDL;
+ }
+
+ mBuddyListMap.insert(buddyListMap::value_type(&(result->mURI), result));
}
return result;
}
-F32 LLVoiceClient::getCurrentPower(const LLUUID& id)
-{
- F32 result = 0;
- participantState *participant = findParticipantByID(id);
- if(participant)
+LLVoiceClient::buddyListEntry *LLVoiceClient::findBuddy(const std::string &uri)
+{
+ buddyListEntry *result = NULL;
+ buddyListMap::iterator iter = mBuddyListMap.find(&uri);
+ if(iter != mBuddyListMap.end())
{
- result = participant->mPower;
+ result = iter->second;
}
return result;
}
-
-std::string LLVoiceClient::getDisplayName(const LLUUID& id)
+LLVoiceClient::buddyListEntry *LLVoiceClient::findBuddy(const LLUUID &id)
{
- std::string result;
- participantState *participant = findParticipantByID(id);
- if(participant)
+ buddyListEntry *result = NULL;
+ buddyListMap::iterator iter;
+
+ for(iter = mBuddyListMap.begin(); iter != mBuddyListMap.end(); iter++)
{
- result = participant->mDisplayName;
+ if(iter->second->mUUID == id)
+ {
+ result = iter->second;
+ break;
+ }
}
return result;
}
-
-BOOL LLVoiceClient::getUsingPTT(const LLUUID& id)
+LLVoiceClient::buddyListEntry *LLVoiceClient::findBuddyByDisplayName(const std::string &name)
{
- BOOL result = FALSE;
+ buddyListEntry *result = NULL;
+ buddyListMap::iterator iter;
- participantState *participant = findParticipantByID(id);
- if(participant)
+ for(iter = mBuddyListMap.begin(); iter != mBuddyListMap.end(); iter++)
{
- // I'm not sure what the semantics of this should be.
- // Does "using PTT" mean they're configured with a push-to-talk button?
- // For now, we know there's no PTT mechanism in place, so nobody is using it.
+ if(iter->second->mDisplayName == name)
+ {
+ result = iter->second;
+ break;
+ }
}
return result;
}
-BOOL LLVoiceClient::getPTTPressed(const LLUUID& id)
+void LLVoiceClient::deleteBuddy(const std::string &uri)
{
- BOOL result = FALSE;
-
- participantState *participant = findParticipantByID(id);
- if(participant)
+ buddyListMap::iterator iter = mBuddyListMap.find(&uri);
+ if(iter != mBuddyListMap.end())
+ {
+ LL_DEBUGS("Voice") << "deleting buddy " << uri << LL_ENDL;
+ buddyListEntry *buddy = iter->second;
+ mBuddyListMap.erase(iter);
+ delete buddy;
+ }
+ else
{
- result = participant->mPTT;
+ LL_DEBUGS("Voice") << "attempt to delete nonexistent buddy " << uri << LL_ENDL;
}
- return result;
}
-BOOL LLVoiceClient::getOnMuteList(const LLUUID& id)
+void LLVoiceClient::deleteAllBuddies(void)
{
- BOOL result = FALSE;
-
- participantState *participant = findParticipantByID(id);
- if(participant)
+ while(!mBuddyListMap.empty())
{
- result = participant->mOnMuteList;
+ deleteBuddy(*(mBuddyListMap.begin()->first));
}
-
- return result;
+
+ // Don't want to correlate with friends list when we've emptied the buddy list.
+ mBuddyListMapPopulated = false;
+
+ // Don't want to correlate with friends list when we've reset the block rules.
+ mBlockRulesListReceived = false;
+ mAutoAcceptRulesListReceived = false;
}
-// External accessiors. Maps 0.0 to 1.0 to internal values 0-400 with .5 == 100
-// internal = 400 * external^2
-F32 LLVoiceClient::getUserVolume(const LLUUID& id)
+void LLVoiceClient::deleteAllBlockRules(void)
{
- F32 result = 0.0f;
-
- participantState *participant = findParticipantByID(id);
- if(participant)
+ // Clear the block list entry flags from all local buddy list entries
+ buddyListMap::iterator buddy_it;
+ for(buddy_it = mBuddyListMap.begin(); buddy_it != mBuddyListMap.end(); buddy_it++)
{
- S32 ires = participant->mUserVolume; // 0-400
- result = sqrtf(((F32)ires) / 400.f);
+ buddy_it->second->mHasBlockListEntry = false;
}
-
- return result;
}
-void LLVoiceClient::setUserVolume(const LLUUID& id, F32 volume)
+void LLVoiceClient::deleteAllAutoAcceptRules(void)
{
- participantState *participant = findParticipantByID(id);
- if (participant)
+ // Clear the auto-accept list entry flags from all local buddy list entries
+ buddyListMap::iterator buddy_it;
+ for(buddy_it = mBuddyListMap.begin(); buddy_it != mBuddyListMap.end(); buddy_it++)
{
- // volume can amplify by as much as 4x!
- S32 ivol = (S32)(400.f * volume * volume);
- participant->mUserVolume = llclamp(ivol, 0, 400);
- participant->mVolumeDirty = TRUE;
- mVolumeDirty = TRUE;
+ buddy_it->second->mHasAutoAcceptListEntry = false;
}
}
-
-
-LLVoiceClient::serviceType LLVoiceClient::getServiceType(const LLUUID& id)
+void LLVoiceClient::addBlockRule(const std::string &blockMask, const std::string &presenceOnly)
{
- serviceType result = serviceTypeUnknown;
+ buddyListEntry *buddy = NULL;
- participantState *participant = findParticipantByID(id);
- if(participant)
+ // blockMask is the SIP URI of a friends list entry
+ buddyListMap::iterator iter = mBuddyListMap.find(&blockMask);
+ if(iter != mBuddyListMap.end())
{
- result = participant->mServiceType;
+ LL_DEBUGS("Voice") << "block list entry for " << blockMask << LL_ENDL;
+ buddy = iter->second;
+ }
+
+ if(buddy == NULL)
+ {
+ LL_DEBUGS("Voice") << "block list entry for unknown buddy " << blockMask << LL_ENDL;
+ buddy = addBuddy(blockMask);
}
- return result;
+ if(buddy != NULL)
+ {
+ buddy->mHasBlockListEntry = true;
+ }
}
-std::string LLVoiceClient::getGroupID(const LLUUID& id)
+void LLVoiceClient::addAutoAcceptRule(const std::string &autoAcceptMask, const std::string &autoAddAsBuddy)
{
- std::string result;
+ buddyListEntry *buddy = NULL;
- participantState *participant = findParticipantByID(id);
- if(participant)
+ // blockMask is the SIP URI of a friends list entry
+ buddyListMap::iterator iter = mBuddyListMap.find(&autoAcceptMask);
+ if(iter != mBuddyListMap.end())
{
- result = participant->mGroupID;
+ LL_DEBUGS("Voice") << "auto-accept list entry for " << autoAcceptMask << LL_ENDL;
+ buddy = iter->second;
+ }
+
+ if(buddy == NULL)
+ {
+ LL_DEBUGS("Voice") << "auto-accept list entry for unknown buddy " << autoAcceptMask << LL_ENDL;
+ buddy = addBuddy(autoAcceptMask);
+ }
+
+ if(buddy != NULL)
+ {
+ buddy->mHasAutoAcceptListEntry = true;
}
-
- return result;
}
-BOOL LLVoiceClient::getAreaVoiceDisabled()
+void LLVoiceClient::accountListBlockRulesResponse(int statusCode, const std::string &statusString)
{
- return mAreaVoiceDisabled;
+ // Block list entries were updated via addBlockRule() during parsing. Just flag that we're done.
+ mBlockRulesListReceived = true;
+}
+
+void LLVoiceClient::accountListAutoAcceptRulesResponse(int statusCode, const std::string &statusString)
+{
+ // Block list entries were updated via addBlockRule() during parsing. Just flag that we're done.
+ mAutoAcceptRulesListReceived = true;
}
void LLVoiceClient::addObserver(LLVoiceClientParticipantObserver* observer)
{
- mObservers.insert(observer);
+ mParticipantObservers.insert(observer);
}
void LLVoiceClient::removeObserver(LLVoiceClientParticipantObserver* observer)
{
- mObservers.erase(observer);
+ mParticipantObservers.erase(observer);
}
-void LLVoiceClient::notifyObservers()
+void LLVoiceClient::notifyParticipantObservers()
{
- for (observer_set_t::iterator it = mObservers.begin();
- it != mObservers.end();
+ for (observer_set_t::iterator it = mParticipantObservers.begin();
+ it != mParticipantObservers.end();
)
{
LLVoiceClientParticipantObserver* observer = *it;
observer->onChange();
// In case onChange() deleted an entry.
- it = mObservers.upper_bound(observer);
+ it = mParticipantObservers.upper_bound(observer);
}
}
-void LLVoiceClient::addStatusObserver(LLVoiceClientStatusObserver* observer)
+void LLVoiceClient::addObserver(LLVoiceClientStatusObserver* observer)
{
mStatusObservers.insert(observer);
}
-void LLVoiceClient::removeStatusObserver(LLVoiceClientStatusObserver* observer)
+void LLVoiceClient::removeObserver(LLVoiceClientStatusObserver* observer)
{
mStatusObservers.erase(observer);
}
void LLVoiceClient::notifyStatusObservers(LLVoiceClientStatusObserver::EStatusType status)
{
- if(status == LLVoiceClientStatusObserver::ERROR_UNKNOWN)
+ if(mAudioSession)
{
- switch(mVivoxErrorStatusCode)
+ if(status == LLVoiceClientStatusObserver::ERROR_UNKNOWN)
{
- case 20713: status = LLVoiceClientStatusObserver::ERROR_CHANNEL_FULL; break;
- case 20714: status = LLVoiceClientStatusObserver::ERROR_CHANNEL_LOCKED; break;
- case 20715:
- //invalid channel, we may be using a set of poorly cached
- //info
- status = LLVoiceClientStatusObserver::ERROR_NOT_AVAILABLE;
- break;
- case 1009:
- //invalid username and password
- status = LLVoiceClientStatusObserver::ERROR_NOT_AVAILABLE;
- break;
- }
-
- // Reset the error code to make sure it won't be reused later by accident.
- mVivoxErrorStatusCode = 0;
- }
-
- if (status == LLVoiceClientStatusObserver::STATUS_LEFT_CHANNEL
- //NOT_FOUND || TEMPORARILY_UNAVAILABLE || REQUEST_TIMEOUT
- && (mVivoxErrorStatusCode == 404 || mVivoxErrorStatusCode == 480 || mVivoxErrorStatusCode == 408))
- {
- // call failed because other user was not available
- // treat this as an error case
- status = LLVoiceClientStatusObserver::ERROR_NOT_AVAILABLE;
+ switch(mAudioSession->mErrorStatusCode)
+ {
+ case 20713: status = LLVoiceClientStatusObserver::ERROR_CHANNEL_FULL; break;
+ case 20714: status = LLVoiceClientStatusObserver::ERROR_CHANNEL_LOCKED; break;
+ case 20715:
+ //invalid channel, we may be using a set of poorly cached
+ //info
+ status = LLVoiceClientStatusObserver::ERROR_NOT_AVAILABLE;
+ break;
+ case 1009:
+ //invalid username and password
+ status = LLVoiceClientStatusObserver::ERROR_NOT_AVAILABLE;
+ break;
+ }
- // Reset the error code to make sure it won't be reused later by accident.
- mVivoxErrorStatusCode = 0;
+ // Reset the error code to make sure it won't be reused later by accident.
+ mAudioSession->mErrorStatusCode = 0;
+ }
+ else if(status == LLVoiceClientStatusObserver::STATUS_LEFT_CHANNEL)
+ {
+ switch(mAudioSession->mErrorStatusCode)
+ {
+ case 404: // NOT_FOUND
+ case 480: // TEMPORARILY_UNAVAILABLE
+ case 408: // REQUEST_TIMEOUT
+ // call failed because other user was not available
+ // treat this as an error case
+ status = LLVoiceClientStatusObserver::ERROR_NOT_AVAILABLE;
+
+ // Reset the error code to make sure it won't be reused later by accident.
+ mAudioSession->mErrorStatusCode = 0;
+ break;
+ }
+ }
}
-
- LL_DEBUGS("Voice") << " " << LLVoiceClientStatusObserver::status2string(status) << ", session URI " << mSessionURI << LL_ENDL;
+
+ LL_DEBUGS("Voice")
+ << " " << LLVoiceClientStatusObserver::status2string(status)
+ << ", session URI " << getAudioSessionURI()
+ << (inSpatialChannel()?", proximal is true":", proximal is false")
+ << LL_ENDL;
for (status_observer_set_t::iterator it = mStatusObservers.begin();
it != mStatusObservers.end();
)
{
LLVoiceClientStatusObserver* observer = *it;
- observer->onChange(status, mSessionURI, !mNonSpatialChannel);
+ observer->onChange(status, getAudioSessionURI(), inSpatialChannel());
// In case onError() deleted an entry.
it = mStatusObservers.upper_bound(observer);
}
}
+void LLVoiceClient::addObserver(LLFriendObserver* observer)
+{
+ mFriendObservers.insert(observer);
+}
+
+void LLVoiceClient::removeObserver(LLFriendObserver* observer)
+{
+ mFriendObservers.erase(observer);
+}
+
+void LLVoiceClient::notifyFriendObservers()
+{
+ for (friend_observer_set_t::iterator it = mFriendObservers.begin();
+ it != mFriendObservers.end();
+ )
+ {
+ LLFriendObserver* observer = *it;
+ it++;
+ // The only friend-related thing we notify on is online/offline transitions.
+ observer->changed(LLFriendObserver::ONLINE);
+ }
+}
+
+void LLVoiceClient::lookupName(const LLUUID &id)
+{
+ gCacheName->getName(id, onAvatarNameLookup);
+}
+
//static
-// void LLVoiceClient::onAvatarNameLookup(const LLUUID& id, const std::string& first, const std::string& last, BOOL is_group, void* user_data)
-// {
-// participantState* statep = gVoiceClient->findParticipantByID(id);
+void LLVoiceClient::onAvatarNameLookup(const LLUUID& id, const std::string& first, const std::string& last, BOOL is_group, void* user_data)
+{
+ if(gVoiceClient)
+ {
+ std::string name = llformat("%s %s", first.c_str(), last.c_str());
+ gVoiceClient->avatarNameResolved(id, name);
+ }
+}
-// if (statep)
-// {
-// statep->mDisplayName = first + " " + last;
-// }
+void LLVoiceClient::avatarNameResolved(const LLUUID &id, const std::string &name)
+{
+ // If the avatar whose name just resolved is on our friends list, resync the friends list.
+ if(LLAvatarTracker::instance().getBuddyInfo(id) != NULL)
+ {
+ mFriendsListDirty = true;
+ }
-// gVoiceClient->notifyObservers();
-// }
+ // Iterate over all sessions.
+ for(sessionIterator iter = sessionsBegin(); iter != sessionsEnd(); iter++)
+ {
+ sessionState *session = *iter;
+
+ // Check for this user as a participant in this session
+ participantState *participant = session->findParticipantByID(id);
+ if(participant)
+ {
+ // Found -- fill in the name
+ participant->mAccountName = name;
+ // and post a "participants updated" message to listeners later.
+ session->mParticipantsChanged = true;
+ }
+
+ // Check whether this is a p2p session whose caller name just resolved
+ if(session->mCallerID == id)
+ {
+ // this session's "caller ID" just resolved. Fill in the name.
+ session->mName = name;
+ if(session->mTextInvitePending)
+ {
+ session->mTextInvitePending = false;
+
+ // We don't need to call gIMMgr->addP2PSession() here. The first incoming message will create the panel.
+ }
+ if(session->mVoiceInvitePending)
+ {
+ session->mVoiceInvitePending = false;
+
+ gIMMgr->inviteToSession(
+ session->mIMSessionID,
+ session->mName,
+ session->mCallerID,
+ session->mName,
+ IM_SESSION_P2P_INVITE,
+ LLIMMgr::INVITATION_TYPE_VOICE,
+ session->mHandle,
+ session->mSIPURI);
+ }
+
+ }
+ }
+}
class LLViewerParcelVoiceInfo : public LLHTTPNode
{
diff --git a/linden/indra/newview/llvoiceclient.h b/linden/indra/newview/llvoiceclient.h
index d0b7839..9fc6a7d 100644
--- a/linden/indra/newview/llvoiceclient.h
+++ b/linden/indra/newview/llvoiceclient.h
@@ -41,6 +41,7 @@ class LLVivoxProtocolParser;
#include "v3math.h"
#include "llframetimer.h"
#include "llviewerregion.h"
+#include "llcallingcard.h" // for LLFriendObserver
class LLVoiceClientParticipantObserver
{
@@ -91,41 +92,10 @@ class LLVoiceClient: public LLSingleton
public:
- enum serviceType
- {
- serviceTypeUnknown, // Unknown, returned if no data on the avatar is available
- serviceTypeA, // spatialized local chat
- serviceTypeB, // remote multi-party chat
- serviceTypeC // one-to-one and small group chat
- };
static F32 OVERDRIVEN_POWER_LEVEL;
void updateSettings(); // call after loading settings and whenever they change
- /////////////////////////////
- // session control messages
- void connect();
-
- void connectorCreate();
- void connectorShutdown();
-
- void requestVoiceAccountProvision(S32 retries = 3);
- void userAuthorized(
- const std::string& firstName,
- const std::string& lastName,
- const LLUUID &agentID);
- void login(const std::string& accountName, const std::string &password);
- void loginSendMessage();
- void logout();
- void logoutSendMessage();
-
- void channelGetListSendMessage();
- void sessionCreateSendMessage();
- void sessionConnectSendMessage();
- void sessionTerminate();
- void sessionTerminateSendMessage();
- void sessionTerminateByHandle(std::string &sessionHandle);
-
void getCaptureDevicesSendMessage();
void getRenderDevicesSendMessage();
@@ -170,23 +140,32 @@ class LLVoiceClient: public LLSingleton
/////////////////////////////
// Response/Event handlers
- void connectorCreateResponse(int statusCode, std::string &statusString, std::string &connectorHandle);
- void loginResponse(int statusCode, std::string &statusString, std::string &accountHandle);
- void channelGetListResponse(int statusCode, std::string &statusString);
- void sessionCreateResponse(int statusCode, std::string &statusString, std::string &sessionHandle);
- void sessionConnectResponse(int statusCode, std::string &statusString);
- void sessionTerminateResponse(int statusCode, std::string &statusString);
+ void connectorCreateResponse(int statusCode, std::string &statusString, std::string &connectorHandle, std::string &versionID);
+ void loginResponse(int statusCode, std::string &statusString, std::string &accountHandle, int numberOfAliases);
+ void sessionCreateResponse(std::string &requestId, int statusCode, std::string &statusString, std::string &sessionHandle);
+ void sessionGroupAddSessionResponse(std::string &requestId, int statusCode, std::string &statusString, std::string &sessionHandle);
+ void sessionConnectResponse(std::string &requestId, int statusCode, std::string &statusString);
void logoutResponse(int statusCode, std::string &statusString);
void connectorShutdownResponse(int statusCode, std::string &statusString);
- void loginStateChangeEvent(std::string &accountHandle, int statusCode, std::string &statusString, int state);
- void sessionNewEvent(std::string &accountHandle, std::string &eventSessionHandle, int state, std::string &nameString, std::string &uriString);
- void sessionStateChangeEvent(std::string &uriString, int statusCode, std::string &statusString, std::string &sessionHandle, int state, bool isChannel, std::string &nameString);
- void participantStateChangeEvent(std::string &uriString, int statusCode, std::string &statusString, int state, std::string &nameString, std::string &displayNameString, int participantType);
- void participantPropertiesEvent(std::string &uriString, int statusCode, std::string &statusString, bool isLocallyMuted, bool isModeratorMuted, bool isSpeaking, int volume, F32 energy);
+ void accountLoginStateChangeEvent(std::string &accountHandle, int statusCode, std::string &statusString, int state);
+ void mediaStreamUpdatedEvent(std::string &sessionHandle, std::string &sessionGroupHandle, int statusCode, std::string &statusString, int state, bool incoming);
+ void textStreamUpdatedEvent(std::string &sessionHandle, std::string &sessionGroupHandle, bool enabled, int state, bool incoming);
+ void sessionAddedEvent(std::string &uriString, std::string &alias, std::string &sessionHandle, std::string &sessionGroupHandle, bool isChannel, bool incoming, std::string &nameString, std::string &applicationString);
+ void sessionGroupAddedEvent(std::string &sessionGroupHandle);
+ void sessionRemovedEvent(std::string &sessionHandle, std::string &sessionGroupHandle);
+ void participantAddedEvent(std::string &sessionHandle, std::string &sessionGroupHandle, std::string &uriString, std::string &alias, std::string &nameString, std::string &displayNameString, int participantType);
+ void participantRemovedEvent(std::string &sessionHandle, std::string &sessionGroupHandle, std::string &uriString, std::string &alias, std::string &nameString);
+ void participantUpdatedEvent(std::string &sessionHandle, std::string &sessionGroupHandle, std::string &uriString, std::string &alias, bool isModeratorMuted, bool isSpeaking, int volume, F32 energy);
void auxAudioPropertiesEvent(F32 energy);
-
+ void buddyPresenceEvent(std::string &uriString, std::string &alias, std::string &statusString, std::string &applicationString);
+ void messageEvent(std::string &sessionHandle, std::string &uriString, std::string &alias, std::string &messageHeader, std::string &messageBody, std::string &applicationString);
+ void sessionNotificationEvent(std::string &sessionHandle, std::string &uriString, std::string ¬ificationType);
+ void subscriptionEvent(std::string &buddyURI, std::string &subscriptionHandle, std::string &alias, std::string &displayName, std::string &applicationString, std::string &subscriptionType);
+
+ void buddyListChanged();
void muteListChanged();
+ void updateFriends(U32 mask);
/////////////////////////////
// Sending updates of current state
@@ -210,7 +189,6 @@ static void updatePosition(void);
void setVoiceVolume(F32 volume);
void setMicGain(F32 volume);
void setUserVolume(const LLUUID& id, F32 volume); // set's volume for specified agent, from 0-1 (where .5 is nominal)
- void setVivoxDebugServerName(std::string &serverName);
void setLipSyncEnabled(BOOL enabled);
BOOL lipSyncEnabled();
@@ -225,57 +203,261 @@ static void updatePosition(void);
BOOL getIsSpeaking(const LLUUID& id);
BOOL getIsModeratorMuted(const LLUUID& id);
F32 getCurrentPower(const LLUUID& id); // "power" is related to "amplitude" in a defined way. I'm just not sure what the formula is...
- BOOL getPTTPressed(const LLUUID& id); // This is the inverse of the "locally muted" property.
BOOL getOnMuteList(const LLUUID& id);
F32 getUserVolume(const LLUUID& id);
std::string getDisplayName(const LLUUID& id);
// MBW -- XXX -- Not sure how to get this data out of the TVC
BOOL getUsingPTT(const LLUUID& id);
- serviceType getServiceType(const LLUUID& id); // type of chat the user is involved in (see bHear scope doc for definitions of A/B/C)
std::string getGroupID(const LLUUID& id); // group ID if the user is in group chat (empty string if not applicable)
/////////////////////////////
BOOL getAreaVoiceDisabled(); // returns true if the area the avatar is in is speech-disabled.
// Use this to determine whether to show a "no speech" icon in the menu bar.
+
+ /////////////////////////////
+ // Recording controls
+ void recordingLoopStart(int seconds = 3600, int deltaFramesPerControlFrame = 200);
+ void recordingLoopSave(const std::string& filename);
+ void recordingStop();
+
+ // Playback controls
+ void filePlaybackStart(const std::string& filename);
+ void filePlaybackStop();
+ void filePlaybackSetPaused(bool paused);
+ void filePlaybackSetMode(bool vox = false, float speed = 1.0f);
+
+
+ // This is used by the string-keyed maps below, to avoid storing the string twice.
+ // The 'const std::string *' in the key points to a string actually stored in the object referenced by the map.
+ // The add and delete operations for each map allocate and delete in the right order to avoid dangling references.
+ // The default compare operation would just compare pointers, which is incorrect, so they must use this comparitor instead.
+ struct stringMapComparitor
+ {
+ bool operator()(const std::string* a, const std::string * b) const
+ {
+ return a->compare(*b) < 0;
+ }
+ };
+ struct uuidMapComparitor
+ {
+ bool operator()(const LLUUID* a, const LLUUID * b) const
+ {
+ return *a < *b;
+ }
+ };
+
struct participantState
{
public:
participantState(const std::string &uri);
+
+ bool updateMuteState();
+
std::string mURI;
- std::string mName;
+ LLUUID mAvatarID;
+ std::string mAccountName;
std::string mDisplayName;
- bool mPTT;
- bool mIsSpeaking;
- bool mIsModeratorMuted;
LLFrameTimer mSpeakingTimeout;
F32 mLastSpokeTimestamp;
F32 mPower;
int mVolume;
- serviceType mServiceType;
std::string mGroupID;
- bool mOnMuteList; // true if this avatar is on the user's mute list (and should be muted)
int mUserVolume;
+ bool mPTT;
+ bool mIsSpeaking;
+ bool mIsModeratorMuted;
+ bool mOnMuteList; // true if this avatar is on the user's mute list (and should be muted)
bool mVolumeDirty; // true if this participant needs a volume command sent (either mOnMuteList or mUserVolume has changed)
bool mAvatarIDValid;
- LLUUID mAvatarID;
+ bool mIsSelf;
+ };
+ typedef std::map participantMap;
+
+ typedef std::map participantUUIDMap;
+
+ enum streamState
+ {
+ streamStateUnknown = 0,
+ streamStateIdle = 1,
+ streamStateConnected = 2,
+ streamStateRinging = 3,
};
- typedef std::map participantMap;
- participantState *findParticipant(const std::string &uri);
- participantState *findParticipantByAvatar(LLVOAvatar *avatar);
+ struct sessionState
+ {
+ public:
+ sessionState();
+ ~sessionState();
+
+ participantState *addParticipant(const std::string &uri);
+ // Note: after removeParticipant returns, the participant* that was passed to it will have been deleted.
+ // Take care not to use the pointer again after that.
+ void removeParticipant(participantState *participant);
+ void removeAllParticipants();
+
+ participantState *findParticipant(const std::string &uri);
+ participantState *findParticipantByID(const LLUUID& id);
+
+ std::string mHandle;
+ std::string mGroupHandle;
+ std::string mSIPURI;
+ std::string mAlias;
+ std::string mName;
+ std::string mAlternateSIPURI;
+ std::string mHash; // Channel password
+ std::string mErrorStatusString;
+ std::queue mTextMsgQueue;
+
+ LLUUID mIMSessionID;
+ LLUUID mCallerID;
+ int mErrorStatusCode;
+ int mMediaStreamState;
+ int mTextStreamState;
+ bool mCreateInProgress; // True if a Session.Create has been sent for this session and no response has been received yet.
+ bool mMediaConnectInProgress; // True if a Session.MediaConnect has been sent for this session and no response has been received yet.
+ bool mVoiceInvitePending; // True if a voice invite is pending for this session (usually waiting on a name lookup)
+ bool mTextInvitePending; // True if a text invite is pending for this session (usually waiting on a name lookup)
+ bool mSynthesizedCallerID; // True if the caller ID is a hash of the SIP URI -- this means we shouldn't do a name lookup.
+ bool mIsChannel; // True for both group and spatial channels (false for p2p, PSTN)
+ bool mIsSpatial; // True for spatial channels
+ bool mIsP2P;
+ bool mIncoming;
+ bool mVoiceEnabled;
+ bool mReconnect; // Whether we should try to reconnect to this session if it's dropped
+ // Set to true when the mute state of someone in the participant list changes.
+ // The code will have to walk the list to find the changed participant(s).
+ bool mVolumeDirty;
+
+ bool mParticipantsChanged;
+ participantMap mParticipantsByURI;
+ participantUUIDMap mParticipantsByUUID;
+ };
+
participantState *findParticipantByID(const LLUUID& id);
-
participantMap *getParticipantList(void);
+
+ typedef std::map sessionMap;
+ typedef std::set sessionSet;
+
+ typedef sessionSet::iterator sessionIterator;
+ sessionIterator sessionsBegin(void);
+ sessionIterator sessionsEnd(void);
+
+ sessionState *findSession(const std::string &handle);
+ sessionState *findSessionBeingCreatedByURI(const std::string &uri);
+ sessionState *findSession(const LLUUID &participant_id);
+ sessionState *findSessionByCreateID(const std::string &create_id);
+
+ sessionState *addSession(const std::string &uri, const std::string &handle = LLStringUtil::null);
+ void setSessionHandle(sessionState *session, const std::string &handle = LLStringUtil::null);
+ void setSessionURI(sessionState *session, const std::string &uri);
+ void deleteSession(sessionState *session);
+ void deleteAllSessions(void);
+
+ void verifySessionState(void);
+
+ void joinedAudioSession(sessionState *session);
+ void leftAudioSession(sessionState *session);
+
+ // This is called in several places where the session _may_ need to be deleted.
+ // It contains logic for whether to delete the session or keep it around.
+ void reapSession(sessionState *session);
+
+ // Returns true if the session seems to indicate we've moved to a region on a different voice server
+ bool sessionNeedsRelog(sessionState *session);
+
+ struct buddyListEntry
+ {
+ buddyListEntry(const std::string &uri);
+ std::string mURI;
+ std::string mDisplayName;
+ LLUUID mUUID;
+ bool mOnlineSL;
+ bool mOnlineSLim;
+ bool mCanSeeMeOnline;
+ bool mHasBlockListEntry;
+ bool mHasAutoAcceptListEntry;
+ bool mNameResolved;
+ bool mInSLFriends;
+ bool mInVivoxBuddies;
+ bool mNeedsNameUpdate;
+ };
+
+ typedef std::map buddyListMap;
+
+ // This should be called when parsing a buddy list entry sent by SLVoice.
+ void processBuddyListEntry(const std::string &uri, const std::string &displayName);
+
+ buddyListEntry *addBuddy(const std::string &uri);
+ buddyListEntry *addBuddy(const std::string &uri, const std::string &displayName);
+ buddyListEntry *findBuddy(const std::string &uri);
+ buddyListEntry *findBuddy(const LLUUID &id);
+ buddyListEntry *findBuddyByDisplayName(const std::string &name);
+ void deleteBuddy(const std::string &uri);
+ void deleteAllBuddies(void);
+
+ void deleteAllBlockRules(void);
+ void addBlockRule(const std::string &blockMask, const std::string &presenceOnly);
+ void deleteAllAutoAcceptRules(void);
+ void addAutoAcceptRule(const std::string &autoAcceptMask, const std::string &autoAddAsBuddy);
+ void accountListBlockRulesResponse(int statusCode, const std::string &statusString);
+ void accountListAutoAcceptRulesResponse(int statusCode, const std::string &statusString);
+
+ /////////////////////////////
+ // session control messages
+ void connectorCreate();
+ void connectorShutdown();
+ void requestVoiceAccountProvision(S32 retries = 3);
+ void userAuthorized(
+ const std::string& firstName,
+ const std::string& lastName,
+ const LLUUID &agentID);
+ void login(
+ const std::string& account_name,
+ const std::string& password,
+ const std::string& voice_sip_uri_hostname,
+ const std::string& voice_account_server_uri);
+ void loginSendMessage();
+ void logout();
+ void logoutSendMessage();
+
+ void accountListBlockRulesSendMessage();
+ void accountListAutoAcceptRulesSendMessage();
+
+ void sessionGroupCreateSendMessage();
+ void sessionCreateSendMessage(sessionState *session, bool startAudio = true, bool startText = false);
+ void sessionGroupAddSessionSendMessage(sessionState *session, bool startAudio = true, bool startText = false);
+ void sessionMediaConnectSendMessage(sessionState *session); // just joins the audio session
+ void sessionTextConnectSendMessage(sessionState *session); // just joins the text session
+ void sessionTerminateSendMessage(sessionState *session);
+ void sessionMediaDisconnectSendMessage(sessionState *session);
+ void sessionTextDisconnectSendMessage(sessionState *session);
+
+ // Pokes the state machine to leave the audio session next time around.
+ void sessionTerminate();
+
+ // Pokes the state machine to shut down the connector and restart it.
+ void requestRelog();
+
+ // Does the actual work to get out of the audio session
+ void leaveAudioSession();
+
void addObserver(LLVoiceClientParticipantObserver* observer);
void removeObserver(LLVoiceClientParticipantObserver* observer);
- void addStatusObserver(LLVoiceClientStatusObserver* observer);
- void removeStatusObserver(LLVoiceClientStatusObserver* observer);
+ void addObserver(LLVoiceClientStatusObserver* observer);
+ void removeObserver(LLVoiceClientStatusObserver* observer);
+
+ void addObserver(LLFriendObserver* observer);
+ void removeObserver(LLFriendObserver* observer);
+
+ void lookupName(const LLUUID &id);
+ static void onAvatarNameLookup(const LLUUID& id, const std::string& first, const std::string& last, BOOL is_group, void* user_data);
+ void avatarNameResolved(const LLUUID &id, const std::string &name);
-// static void onAvatarNameLookup(const LLUUID& id, const std::string& first, const std::string& last, BOOL is_group, void* user_data);
typedef std::vector deviceList;
deviceList *getCaptureDevices();
@@ -287,8 +469,16 @@ static void updatePosition(void);
void setSpatialChannel(
const std::string &uri,
const std::string &credentials);
- void callUser(LLUUID &uuid);
- void answerInvite(std::string &sessionHandle, LLUUID& other_user_id);
+ // start a voice session with the specified user
+ void callUser(const LLUUID &uuid);
+
+ // Send a text message to the specified user, initiating the session if necessary.
+ bool sendTextMessage(const LLUUID& participant_id, const std::string& message);
+
+ // close any existing text IM session with the specified user
+ void endUserIMSession(const LLUUID &uuid);
+
+ bool answerInvite(std::string &sessionHandle);
void declineInvite(std::string &sessionHandle);
void leaveNonSpatialChannel();
@@ -301,7 +491,11 @@ static void updatePosition(void);
bool inProximalChannel();
std::string sipURIFromID(const LLUUID &id);
-
+
+ // Returns true if the indicated user is online via SIP presence according to SLVoice.
+ // Note that we only get SIP presence data for other users that are in our vivox buddy list.
+ bool isOnlineSIP(const LLUUID &id);
+
private:
// internal state for a simple state machine. This is used to deal with the asynchronous nature of some of the messages.
@@ -313,6 +507,7 @@ static void updatePosition(void);
stateDaemonLaunched, // Daemon has been launched
stateConnecting, // connect() call has been issued
stateIdle, // socket is connected, ready for messaging
+ stateNeedsProvision, // Need to do a ProvisionVoiceAccountRequest
stateConnectorStart, // connector needs to be started
stateConnectorStarting, // waiting for connector handle
stateConnectorStarted, // connector handle received
@@ -322,12 +517,11 @@ static void updatePosition(void);
stateNeedsLogin, // send login request
stateLoggingIn, // waiting for account handle
stateLoggedIn, // account handle received
+ stateCreatingSessionGroup, // Creating the main session group
stateNoChannel, //
stateMicTuningStart,
stateMicTuningRunning,
stateMicTuningStop,
- stateSessionCreate, // need to send Session.Create command
- stateSessionConnect, // need to send Session.Connect command
stateJoiningSession, // waiting for session handle
stateSessionJoined, // session handle received
stateRunning, // in session, steady state
@@ -354,7 +548,7 @@ static void updatePosition(void);
state mState;
bool mSessionTerminateRequested;
- bool mNonSpatialChannel;
+ bool mRelogRequested;
void setState(state inState);
state getState(void) { return mState; };
@@ -377,18 +571,7 @@ static void updatePosition(void);
std::string mAccountDisplayName;
std::string mAccountFirstName;
std::string mAccountLastName;
-
- std::string mNextP2PSessionURI; // URI of the P2P session to join next
- std::string mNextSessionURI; // URI of the session to join next
- std::string mNextSessionHandle; // Session handle of the session to join next
- std::string mNextSessionHash; // Password hash for the session to join next
- bool mNextSessionSpatial; // Will next session be a spatial chat?
- bool mNextSessionNoReconnect; // Next session should not auto-reconnect (i.e. user -> user chat)
- bool mNextSessionResetOnClose; // If this is true, go back to spatial chat when the next session terminates.
-
- std::string mSessionStateEventHandle; // session handle received in SessionStateChangeEvents
- std::string mSessionStateEventURI; // session URI received in SessionStateChangeEvents
-
+
bool mTuningMode;
float mTuningEnergy;
std::string mTuningAudioFile;
@@ -399,32 +582,40 @@ static void updatePosition(void);
state mTuningExitState; // state to return to when we leave tuning mode.
std::string mSpatialSessionURI;
-
- bool mSessionResetOnClose;
-
- int mVivoxErrorStatusCode;
- std::string mVivoxErrorStatusString;
+ std::string mSpatialSessionCredentials;
+
+ std::string mMainSessionGroupHandle; // handle of the "main" session group.
std::string mChannelName; // Name of the channel to be looked up
bool mAreaVoiceDisabled;
- std::string mSessionURI; // URI of the session we're in.
- bool mSessionP2P; // true if this session is a p2p call
+ sessionState *mAudioSession; // Session state for the current audio session
+ bool mAudioSessionChanged; // set to true when the above pointer gets changed, so observers can be notified.
+
+ sessionState *mNextAudioSession; // Session state for the audio session we're trying to join
+
+// std::string mSessionURI; // URI of the session we're in.
+// std::string mSessionHandle; // returned by ?
S32 mCurrentParcelLocalID; // Used to detect parcel boundary crossings
std::string mCurrentRegionName; // Used to detect parcel boundary crossings
std::string mConnectorHandle; // returned by "Create Connector" message
std::string mAccountHandle; // returned by login message
- std::string mSessionHandle; // returned by ?
+ int mNumberOfAliases;
U32 mCommandCookie;
- std::string mAccountServerName;
- std::string mAccountServerURI;
+ std::string mVoiceAccountServerURI;
+ std::string mVoiceSIPURIHostName;
int mLoginRetryCount;
- participantMap mParticipantMap;
- bool mParticipantMapChanged;
+ sessionMap mSessionsByHandle; // Active sessions, indexed by session handle. Sessions which are being initiated may not be in this map.
+ sessionSet mSessions; // All sessions, not indexed. This is the canonical session list.
+
+ bool mBuddyListMapPopulated;
+ bool mBlockRulesListReceived;
+ bool mAutoAcceptRulesListReceived;
+ buddyListMap mBuddyListMap;
deviceList mCaptureDevices;
deviceList mRenderDevices;
@@ -434,40 +625,41 @@ static void updatePosition(void);
bool mCaptureDeviceDirty;
bool mRenderDeviceDirty;
- participantState *addParticipant(const std::string &uri);
- // Note: after removeParticipant returns, the participant* that was passed to it will have been deleted.
- // Take care not to use the pointer again after that.
- void removeParticipant(participantState *participant);
- void removeAllParticipants();
-
- void updateMuteState(participantState *participant);
-
- typedef std::map channelMap;
- channelMap mChannelMap;
-
- // These are used by the parser when processing a channel list response.
- void clearChannelMap(void);
- void addChannelMapEntry(std::string &name, std::string &uri);
- std::string findChannelURI(std::string &name);
-
// This should be called when the code detects we have changed parcels.
// It initiates the call to the server that gets the parcel channel.
void parcelChanged();
- void switchChannel(std::string uri = std::string(), bool spatial = true, bool noReconnect = false, std::string hash = "");
- void joinSession(std::string handle, std::string uri);
+ void switchChannel(std::string uri = std::string(), bool spatial = true, bool no_reconnect = false, bool is_p2p = false, std::string hash = "");
+ void joinSession(sessionState *session);
- std::string nameFromAvatar(LLVOAvatar *avatar);
- std::string nameFromID(const LLUUID &id);
- bool IDFromName(const std::string name, LLUUID &uuid);
- std::string displayNameFromAvatar(LLVOAvatar *avatar);
+static std::string nameFromAvatar(LLVOAvatar *avatar);
+static std::string nameFromID(const LLUUID &id);
+static bool IDFromName(const std::string name, LLUUID &uuid);
+static std::string displayNameFromAvatar(LLVOAvatar *avatar);
std::string sipURIFromAvatar(LLVOAvatar *avatar);
std::string sipURIFromName(std::string &name);
+
+ // Returns the name portion of the SIP URI if the string looks vaguely like a SIP URI, or an empty string if not.
+static std::string nameFromsipURI(const std::string &uri);
+
+ bool inSpatialChannel(void);
+ std::string getAudioSessionURI();
+ std::string getAudioSessionHandle();
void sendPositionalUpdate(void);
void buildSetCaptureDevice(std::ostringstream &stream);
void buildSetRenderDevice(std::ostringstream &stream);
+ void buildLocalAudioUpdates(std::ostringstream &stream);
+
+ void clearAllLists();
+ void checkFriend(const LLUUID& id);
+ void sendFriendsListUpdates();
+
+ // start a text IM session with the specified user
+ // This will be asynchronous, the session may be established at a future time.
+ sessionState* startUserIMSession(const LLUUID& uuid);
+ void sendQueuedTextMessages(sessionState *session);
void enforceTether(void);
@@ -491,10 +683,9 @@ static void updatePosition(void);
bool mPTTIsToggle;
bool mUserPTTState;
bool mMuteMic;
-
- // Set to true when the mute state of someone in the participant list changes.
- // The code will have to walk the list to find the changed participant(s).
- bool mVolumeDirty;
+
+ // Set to true when the friends list is known to have changed.
+ bool mFriendsListDirty;
enum
{
@@ -522,14 +713,18 @@ static void updatePosition(void);
BOOL mLipSyncEnabled;
typedef std::set observer_set_t;
- observer_set_t mObservers;
+ observer_set_t mParticipantObservers;
- void notifyObservers();
+ void notifyParticipantObservers();
typedef std::set status_observer_set_t;
status_observer_set_t mStatusObservers;
void notifyStatusObservers(LLVoiceClientStatusObserver::EStatusType status);
+
+ typedef std::set friend_observer_set_t;
+ friend_observer_set_t mFriendObservers;
+ void notifyFriendObservers();
};
extern LLVoiceClient *gVoiceClient;
diff --git a/linden/indra/newview/llwindebug.cpp b/linden/indra/newview/llwindebug.cpp
index b595073..4e326ed 100644
--- a/linden/indra/newview/llwindebug.cpp
+++ b/linden/indra/newview/llwindebug.cpp
@@ -121,6 +121,14 @@ MODULE32_NEST Module32Next_;
#define CALL_TRACE_MAX ((DUMP_SIZE_MAX - 2000) / (MAX_PATH + 40)) //max number of traced calls
#define NL L"\r\n" //new line
+
+typedef struct STACK
+{
+ STACK * Ebp;
+ PBYTE Ret_Addr;
+ DWORD Param[0];
+} STACK, * PSTACK;
+
BOOL WINAPI Get_Module_By_Ret_Addr(PBYTE Ret_Addr, LPWSTR Module_Name, PBYTE & Module_Addr);
void WINAPI Get_Call_Stack(const EXCEPTION_RECORD* exception_record,
const CONTEXT* context_record,
@@ -337,6 +345,31 @@ PBYTE get_valid_frame(PBYTE esp)
return NULL;
}
+
+bool shouldUseStackWalker(PSTACK Ebp, int max_depth)
+{
+ WCHAR Module_Name[MAX_PATH];
+ PBYTE Module_Addr = 0;
+ int depth = 0;
+
+ while (depth < max_depth)
+ {
+ if (IsBadReadPtr(Ebp, sizeof(PSTACK)) ||
+ IsBadReadPtr(Ebp->Ebp, sizeof(PSTACK)) ||
+ Ebp->Ebp < Ebp ||
+ Ebp->Ebp - Ebp > 0xFFFFFF ||
+ IsBadCodePtr(FARPROC(Ebp->Ebp->Ret_Addr)) ||
+ !Get_Module_By_Ret_Addr(Ebp->Ebp->Ret_Addr, Module_Name, Module_Addr))
+ {
+ return true;
+ }
+ depth++;
+ Ebp = Ebp->Ebp;
+ }
+
+ return false;
+}
+
//******************************************************************
void WINAPI Get_Call_Stack(const EXCEPTION_RECORD* exception_record,
const CONTEXT* context_record,
@@ -354,17 +387,10 @@ void WINAPI Get_Call_Stack(const EXCEPTION_RECORD* exception_record,
bool fake_frame = false;
bool ebp_used = false;
- const int HEURISTIC_MAX_WALK = 10;
+ const int HEURISTIC_MAX_WALK = 20;
int heuristic_walk_i = 0;
int Ret_Addr_I = 0;
- typedef struct STACK
- {
- STACK * Ebp;
- PBYTE Ret_Addr;
- DWORD Param[0];
- } STACK, * PSTACK;
-
STACK Stack = {0, 0};
PSTACK Ebp;
@@ -433,10 +459,9 @@ void WINAPI Get_Call_Stack(const EXCEPTION_RECORD* exception_record,
// is next ebp valid?
// only run if we've never found a good ebp
+ // and make sure the one after is valid as well
if( !ebp_used &&
- (IsBadReadPtr(Ebp->Ebp, sizeof(PSTACK)) ||
- IsBadCodePtr(FARPROC(Ebp->Ebp->Ret_Addr)) ||
- !Get_Module_By_Ret_Addr(Ebp->Ebp->Ret_Addr, Module_Name, Module_Addr)))
+ shouldUseStackWalker(Ebp, 2))
{
heuristic_walk_i++;
PBYTE new_ebp = get_valid_frame(Esp);
@@ -451,9 +476,9 @@ void WINAPI Get_Call_Stack(const EXCEPTION_RECORD* exception_record,
Ebp = Ebp->Ebp;
}
}
-
+/* TODO remove or turn this code back on to edit the stack after i see a few raw ones. -Palmer
// Now go back through and edit out heuristic stacks that could very well be bogus.
- // Leave the top and the last stack chosen by the heuristic, however.
+ // Leave the top and the last 3 stack chosen by the heuristic, however.
if(heuristic_walk_i > 2)
{
info["CallStack"][0] = tmp_info["CallStack"][0];
@@ -470,7 +495,10 @@ void WINAPI Get_Call_Stack(const EXCEPTION_RECORD* exception_record,
{
info = tmp_info;
}
-
+*/
+ info = tmp_info;
+ info["HeuristicWalkI"] = heuristic_walk_i;
+ info["EbpUsed"] = ebp_used;
} //Get_Call_Stack
diff --git a/linden/indra/newview/skins/default/xui/de/alerts.xml b/linden/indra/newview/skins/default/xui/de/alerts.xml
index fe984ae..68b4507 100644
--- a/linden/indra/newview/skins/default/xui/de/alerts.xml
+++ b/linden/indra/newview/skins/default/xui/de/alerts.xml
@@ -625,7 +625,7 @@ Damit Waffen funktionieren, müssen Skripts erlaubt sein.
- Geben Sie die E-Mail-Adresse des Empfängers ein.
+ Bitte geben Sie für den/die Empfänger eine gültige Email-Adresse ein.
@@ -4537,6 +4537,9 @@ Entspricht dem Azimut.
+
+ Wenn Sie dieser Anfrage zustimmen, erhält das Skript die Erlaubnis, regelmäßig Linden-Dollar (L$) von Ihrem Konto abzubuchen. Diese Erlaubnis kann nur zurückgezogen werden, wenn der Eigentümer das Objekt löscht oder die Skripts in dem Objekt zurücksetzt.
+
diff --git a/linden/indra/newview/skins/default/xui/de/floater_about.xml b/linden/indra/newview/skins/default/xui/de/floater_about.xml
index 1b7722f..82df651 100644
--- a/linden/indra/newview/skins/default/xui/de/floater_about.xml
+++ b/linden/indra/newview/skins/default/xui/de/floater_about.xml
@@ -26,7 +26,7 @@ Alle Rechte vorbehalten. Details siehe licenses.txt.
Voice-Chat-Audiocoding: Polycom(R) Siren14(TM) (ITU-T Empf.G.722.1 Anhang C)
-Wir können nur eine kurze Distanz in die Zukunft blicken, aber dort können wir eine Menge sehen, was getan werden muss. --Alan Turing
+I get by with a little help from my friends. (etwa: Ich überlebe mit ein bisschen Unterstützung von meinen Freunden.) --Richard Starkey
Sie befinden sich in [POSITION]
diff --git a/linden/indra/newview/skins/default/xui/de/panel_group_land_money.xml b/linden/indra/newview/skins/default/xui/de/panel_group_land_money.xml
index 247051f..eee6c5c 100644
--- a/linden/indra/newview/skins/default/xui/de/panel_group_land_money.xml
+++ b/linden/indra/newview/skins/default/xui/de/panel_group_land_money.xml
@@ -1,7 +1,7 @@
- Es werden Parzellen in Gruppenbesitz und Beitragsdetails angezeigt. Solange der Wert für „Insgesamt verwendetes Land“ unter oder gleich dem Wert für „Gesamtbeitrag“ ist, wird eine Warnung angezeigt. Die Registerkarten „Details“ und „Verkäufe“ enthalten Informationen über die Gruppenfinanzen.
+ Es werden Parzellen in Gruppenbesitz und Beitragsdetails angezeigt. Ein Warnhinweis wird angezeigt, solange der Wert für das Insgesamt verwendete Land gleich oder weniger ist als der Gesamtbeitrag. Die Reiter „Details“ und „Verkäufe“ enthalten Informationen über die Gruppenfinanzen.
diff --git a/linden/indra/newview/skins/default/xui/de/panel_voice_controls.xml b/linden/indra/newview/skins/default/xui/de/panel_voice_controls.xml
index 80eb0e7..9674161 100644
--- a/linden/indra/newview/skins/default/xui/de/panel_voice_controls.xml
+++ b/linden/indra/newview/skins/default/xui/de/panel_voice_controls.xml
@@ -1,12 +1,12 @@
+
-
+
diff --git a/linden/indra/newview/skins/default/xui/en-us/panel_audio_device.xml b/linden/indra/newview/skins/default/xui/en-us/panel_audio_device.xml
index 39820e5..58e427f 100644
--- a/linden/indra/newview/skins/default/xui/en-us/panel_audio_device.xml
+++ b/linden/indra/newview/skins/default/xui/en-us/panel_audio_device.xml
@@ -42,7 +42,7 @@
Adjust the slider to control how loud you sound to other Residents. To test the input level, simply speak into your microphone.
-
-
+
+
diff --git a/linden/indra/newview/skins/default/xui/en-us/panel_voice_remote_expanded.xml b/linden/indra/newview/skins/default/xui/en-us/panel_voice_remote_expanded.xml
index 7464467..61a7480 100644
--- a/linden/indra/newview/skins/default/xui/en-us/panel_voice_remote_expanded.xml
+++ b/linden/indra/newview/skins/default/xui/en-us/panel_voice_remote_expanded.xml
@@ -2,7 +2,7 @@
-
+
diff --git a/linden/indra/newview/skins/default/xui/ja/panel_group_land_money.xml b/linden/indra/newview/skins/default/xui/ja/panel_group_land_money.xml
index 9d76935..9331071 100644
--- a/linden/indra/newview/skins/default/xui/ja/panel_group_land_money.xml
+++ b/linden/indra/newview/skins/default/xui/ja/panel_group_land_money.xml
@@ -1,19 +1,15 @@
-
+
-
- グループ所有の区画が資金提供の詳細と共にリストに示されます。
- 土地利用の合計が、
-資金提供の合計以下になるまで警告が表示されます。
- プランニング、詳細、販売の各タブには、
-グループ財政に関する情報が表示されます。
-
-
-
+
+ グループ所有の土地は貢献値の詳細と共に表示されます。土地利用の合計が寄付総額かそれ以下になるまでは警告が表示されます。「詳細」と「販売」タブにグループの資金に関する情報が表示されます。
+
+
+
あなたはグループ所有の土地表示を許されていません。
-
-
+
+
あなたはグループの会計情報の表示を許されていません。
-
+
ロード中...
@@ -21,13 +17,12 @@
グループの保有地
-
-
-
-
+
+
+
+
-
+
寄付総額:
@@ -49,9 +44,6 @@
あなたの貢献:
-
- 平方メートル
-
土地の出資設定を行うことができませんでした。
@@ -65,28 +57,19 @@
グループL$
-
-
- 計算中…
-
-
計算中…
-
-
+
+
計算中…
-
-
+
+
diff --git a/linden/indra/newview/skins/default/xui/ja/panel_group_notices.xml b/linden/indra/newview/skins/default/xui/ja/panel_group_notices.xml
index 66ef789..35b072a 100644
--- a/linden/indra/newview/skins/default/xui/ja/panel_group_notices.xml
+++ b/linden/indra/newview/skins/default/xui/ja/panel_group_notices.xml
@@ -1,45 +1,44 @@
-
+
-
+
通知機能を使うと、グループ内ですばやく情報を伝達できます。
複数のあて先にメッセージを送信できるほか、
-必要に応じてアイテムをメッセージに添付することもできます。 通知は、
-受信アビリティがある役割のグループ・メンバーにのみ配信されます。
- 通知をオフにするには、一般タブを使います。
-
-
+必要に応じてアイテムをメッセージに添付することもできます。
+通知は、受信アビリティがある役割のグループ・メンバーにのみ配信されます。
+通知をオフにするには、一般タブを使います。
+
+
過去の通知はありません。
-
-
+
+
グループ通知アーカイブ
- 通知は、14日間保存されます。閲覧したい通知をクリックしてください。
+ 通知は14日間保存されます。閲覧したい通知をクリックしてください。
新着の通知をチェックするには、[更新]ボタンをクリックします。
ただし、各グループの通知リストは1日あたり200通に制限されます。
-
-
-
-
+
+
+
+
何も見つかりませんでした。
-
-
+
+
通知を作成
- 通知を送るには、件名を入力します。 持ち物のアイテムを
-1つ、このパネルにドラッグして、通知に添付
-できます。 添付できるのは、コピー、譲渡可能なアイテ
-ムで、フォルダーに送ることはできません。
+ 件名を入力して通知を送ります。
+持ち物のアイテムを1つこのパネルにドラッグして、
+通知に添付することができます。 添付できるのはコピー、
+譲渡が可能なアイテムで、フォルダを送ることはできません。
件名:
@@ -50,10 +49,9 @@
添付:
-
-
-
+
+
+
@@ -68,7 +66,6 @@
メッセージ:
-
+
diff --git a/linden/indra/newview/skins/default/xui/ja/panel_group_roles.xml b/linden/indra/newview/skins/default/xui/ja/panel_group_roles.xml
index 0208331..5ed3bf7 100644
--- a/linden/indra/newview/skins/default/xui/ja/panel_group_roles.xml
+++ b/linden/indra/newview/skins/default/xui/ja/panel_group_roles.xml
@@ -1,15 +1,12 @@
-
+
-
+
現在のサブタブに、未適用の変更があります。
-
-
+
+
これらの変更を適用しますか?
-
-
- 「全員」と「オーナー」は特別な役割なので、削除できません。
-
-
+
+
メンバーと役割
@@ -47,52 +44,52 @@
-
-
+
+
-
-
-
+
+
+
-
-
-
+
+
+
あなたはメンバーに割り当てられた役割を付加、削除できます。
Ctrl キーを押しながらメンバー名をクリックすると
複数の人を選択できます。
-
+
-
-
+
+
-
-
-
+
+
+
-
-
-
+
+
+
役割にはタイトルがあり、メンバーが行使可能な
能力のリストが定義されます。 メンバーは、
1つまたは複数の役割に属することができます。 1つのグループに対し、
「全員」と「オーナー」の役割を含めて最高で10の役割を持たせることができます。
-
+
「全員」と「オーナー」は特別な役割なので、削除できません。
-
-
+
+
-
-
+
+
-
+
このグループ内で役割を与えられているメンバーが実行できる操作は、
能力によって決まります。 さまざまな能力が用意されています。
-
+
@@ -103,13 +100,12 @@ Ctrl キーを押しながらメンバー名をクリックすると
許可された能力
-
-
+
+
-
-
-
+
+
+
@@ -134,17 +130,14 @@ Ctrl キーを押しながらメンバー名をクリックすると
割当られたメンバー
-
+
許可された能力
-
-
-
-
-
+
+
+
+
+
@@ -161,4 +154,4 @@ Ctrl キーを押しながらメンバー名をクリックすると
能力のあるメンバー
-
+
\ No newline at end of file
diff --git a/linden/indra/newview/skins/default/xui/ja/panel_preferences_general.xml b/linden/indra/newview/skins/default/xui/ja/panel_preferences_general.xml
index e891986..4c00e9d 100644
--- a/linden/indra/newview/skins/default/xui/ja/panel_preferences_general.xml
+++ b/linden/indra/newview/skins/default/xui/ja/panel_preferences_general.xml
@@ -79,29 +79,35 @@
English (英語)
-
- 中文 (简体) (中国語) – ベータ
+
+ Dansk (デンマーク語) – ベータ
Deutsch (ドイツ語) – ベータ
+
+ Español (スペイン語) – ベータ
+
Français (フランス語) – ベータ
-
- 日本語 – ベータ
-
-
- 한국어 (韓国語) – ベータ
+
+ Magyar (ハンガリー語) - ベータ
-
- Dansk (デンマーク語) - Beta
+
+ Polski (ポーランド語) - ベータ
Português (ポルトガル語) – ベータ
-
- Español (スペイン語) – ベータ
+
+ 中文 (简体) (中国語) - ベータ
+
+
+ 日本語 – ベータ
+
+
+ 한국어 (韓国語) – ベータ
diff --git a/linden/indra/newview/skins/default/xui/pt/alerts.xml b/linden/indra/newview/skins/default/xui/pt/alerts.xml
index a384f60..2a56fd5 100644
--- a/linden/indra/newview/skins/default/xui/pt/alerts.xml
+++ b/linden/indra/newview/skins/default/xui/pt/alerts.xml
@@ -719,7 +719,7 @@ Pagando mais, faz com que seu anúncio apareça em posição mais alta na lista
- Você gostaria de reabilitar todas estas popups que você indicou previamente como 'Não mostrar-me novamente'?
+ Você gostaria de reabilitar todas estas pop ups que você indicou previamente como 'Não me mostrar novamente'?
- Essas configurações ajustar a forma como o ambiente parece localmente no seu computador. Sua placa de vídeo precisa suportar o sombreador atmosférico, a fim de ter acesso a todas as definições.
+ Essas configurações ajustam a forma como o ambiente parece localmente no seu computador. Sua placa de vídeo precisa suportar o sombreador atmosférico, a fim de ter acesso a todas as definições.
Ajuste o controle gradual "Hora do Dia" para alterar o dia da fase localmente sobre o espectador.
@@ -3809,7 +3809,7 @@ Ajuste o controle gradual de "Cobertura das nuvens" para controlar qua
Pegue uma cor da paleta de cores do "Cord a Água" para mudar a cor desta.
-Ajuste o controle gradual de "Névoa de Água" para controlar o quão denso é a névoa dentro da água.
+Ajuste o controle gradual de "Névoa de Água" para controlar o quão densa é a névoa dentro da água.
Clique "Usar Horário do Terreno" para redefinir a hora do dia para a região para o horário atual do dia e permanecer ligados a ela.
@@ -3822,15 +3822,15 @@ Clique "Água Avançada " para abrir um editor com configurações mai
Este editor de dia dá a você o controle sobre o ciclo de dia/noite do Second Life. Este é o ciclo usado pelo editor básico de clima do controle gradual da hora do dia.
-O editor do ciclo de dia trabalha configurado por keyframes (quadros-chave). Estes são pontos (representados pelos ícones cinza no gráfico de horário) que possui o padrão de céu associado a eles. Conforme o dia passa, o céu de Windlight " anima " a intersecção entre esses keyframes (quadros-chave).
+O editor do ciclo de dia trabalha configurado por keyframes (quadros-chave). Estes são pontos (representados pelos ícones cinza no gráfico de horário) que possuem o padrão de céu associado a eles. Conforme o dia passa, o céu de Windlight " anima " a intersecção entre esses keyframes (quadros-chave).
A seta amarela acima da linha de tempo representa a sua vista atua, baseada no horário do dia. Clique e arraste para ver como o seu dia será animado. Você pode adicionar ou deletar as keyframes (quadro-chave) pressionando os botões Adicionar Chave e Deletar chave ao lado direito da linha de tempo.
Você pode configurar a posição do tempo de uma keyframe (quadro-chave) arrastando-o pela linha do tempo, ou configurando manualmente no quadro de configurações do seu keyframe (quadro-chave), será possível a você associar o seu keyframe a este respectivo padrão WindLight.
-A duração do ciclo determina a duração geral do "dia". Configurá-la para um valor baixo (por exmplo, 2 min.) quer dizer que a linha do tempo de 24 horas será animada completamente em apenas dois minutos reais! Assim que estiveres satisfeito com a linha do tempo e o ciclo do keyframe (quadro chave), use os botões Play e Stop para uma prévia de como ficará o resultado. Lembre-se você também pode utilizar a seta amarela indicadora do tempo acima da linha do tempo par aver o ciclo animado interativamente. Usando o botão do tempo do terreno irá sincronizá-lo a duração do seu dia ao ciclo diário do terreno.
+A duração do ciclo determina a duração geral do "dia". Configurá-la para um valor baixo (por exmplo, 2 min.) quer dizer que a linha do tempo de 24 horas será animada completamente em apenas dois minutos reais! Assim que estiveres satisfeito com a linha do tempo e o ciclo do keyframe (quadro chave), use os botões Play e Stop para uma prévia de como ficará o resultado. Lembre-se você também pode utilizar a seta amarela indicadora do tempo acima da linha do tempo para ver o ciclo animado interativamente. Usando o botão do tempo do terreno irá sincronizar a duração do seu dia ao ciclo diário do terreno.
-Assim que estiver satisfeito com o seu ciclo diário, pode salvá-lo ou carregar no botão de teste de dia . Note que agora nós permitimos apenas um Ciclo de dia.
+Assim que estiver satisfeito com o seu ciclo diário, pode salvá-lo ou carregar no botão de teste de dia . Note que agora nós permitimos apenas um Ciclo de dia.
@@ -3942,7 +3942,7 @@ Similar ao azimute.
- Marque esta check box para permitir a reprodução do nas nuvens classicas mais velhas do Second Life, além das nuvens WindLight.
+ Marque esta check box para permitir a reprodução das nuvens clássicas mais velhas do Second Life, além das nuvens WindLight.
diff --git a/linden/indra/newview/skins/default/xui/pt/floater_about_land.xml b/linden/indra/newview/skins/default/xui/pt/floater_about_land.xml
index 51064bc..c61ef4b 100644
--- a/linden/indra/newview/skins/default/xui/pt/floater_about_land.xml
+++ b/linden/indra/newview/skins/default/xui/pt/floater_about_land.xml
@@ -344,7 +344,7 @@ Vá para o menu Mundo > Sobre a Terra ou selecione outro terreno para mostrar
Substituir
-Texture:
+Textura:
@@ -353,8 +353,8 @@ a página web depois que você clicar na seta de
reproduzir.)
- Mídia
-Options:
+ Opções de
+Mídia:
@@ -387,7 +387,7 @@ Options:
Usar um canal especial privado
- Desabilitar audio espacial neste terreno
+ Desabilitar áudio espacial neste terreno
diff --git a/linden/indra/newview/skins/default/xui/pt/floater_day_cycle_options.xml b/linden/indra/newview/skins/default/xui/pt/floater_day_cycle_options.xml
index bf99231..a76d06e 100644
--- a/linden/indra/newview/skins/default/xui/pt/floater_day_cycle_options.xml
+++ b/linden/indra/newview/skins/default/xui/pt/floater_day_cycle_options.xml
@@ -4,7 +4,7 @@
- 12:00 meia noite
+ 12:00 meia-noite
3:00 da manhã
diff --git a/linden/indra/newview/skins/default/xui/pt/floater_im.xml b/linden/indra/newview/skins/default/xui/pt/floater_im.xml
index 81090d5..2d1769e 100644
--- a/linden/indra/newview/skins/default/xui/pt/floater_im.xml
+++ b/linden/indra/newview/skins/default/xui/pt/floater_im.xml
@@ -7,7 +7,7 @@
[FIRST] [LAST] está offline.
- Clique no botão [BUTTON NAME] para aceitar/ conectar para esse pate- papo em voz.
+ Clique no botão [BUTTON NAME] para aceitar/ conectar para esse bate-papo em voz.
Você emudeceu este residente. Enviar uma mensagem vai automaticamente reativá-lo novamente .
diff --git a/linden/indra/newview/skins/default/xui/pt/floater_lagmeter.xml b/linden/indra/newview/skins/default/xui/pt/floater_lagmeter.xml
index d0f0d40..1e90862 100644
--- a/linden/indra/newview/skins/default/xui/pt/floater_lagmeter.xml
+++ b/linden/indra/newview/skins/default/xui/pt/floater_lagmeter.xml
@@ -63,7 +63,7 @@
Causa possível: Carregamento de Imagens
- Causa possível: Muitas imagens na memoria
+ Causa possível: Muitas imagens na memória
Causa possível: Muitos objetos complexos na cena
diff --git a/linden/indra/newview/skins/default/xui/pt/floater_media_browser.xml b/linden/indra/newview/skins/default/xui/pt/floater_media_browser.xml
index c2387d8..d684b75 100644
--- a/linden/indra/newview/skins/default/xui/pt/floater_media_browser.xml
+++ b/linden/indra/newview/skins/default/xui/pt/floater_media_browser.xml
@@ -9,11 +9,11 @@
-
+
-
+
diff --git a/linden/indra/newview/skins/default/xui/pt/floater_preview_notecard_keep_discard.xml b/linden/indra/newview/skins/default/xui/pt/floater_preview_notecard_keep_discard.xml
index c5ba6b2..a42280a 100644
--- a/linden/indra/newview/skins/default/xui/pt/floater_preview_notecard_keep_discard.xml
+++ b/linden/indra/newview/skins/default/xui/pt/floater_preview_notecard_keep_discard.xml
@@ -9,7 +9,7 @@
- Não foi possível encontrar o objeto que contem esta nota.:
+ Não foi possível encontrar o objeto que contém esta nota.:
Você não tem permissão para ler esta nota.
diff --git a/linden/indra/newview/skins/default/xui/pt/floater_snapshot.xml b/linden/indra/newview/skins/default/xui/pt/floater_snapshot.xml
index 9180ae6..83bd2ca 100644
--- a/linden/indra/newview/skins/default/xui/pt/floater_snapshot.xml
+++ b/linden/indra/newview/skins/default/xui/pt/floater_snapshot.xml
@@ -29,8 +29,8 @@
-
-
+
+
Tamanho
diff --git a/linden/indra/newview/skins/default/xui/pt/floater_windlight_options.xml b/linden/indra/newview/skins/default/xui/pt/floater_windlight_options.xml
index e42137b..ff22a87 100644
--- a/linden/indra/newview/skins/default/xui/pt/floater_windlight_options.xml
+++ b/linden/indra/newview/skins/default/xui/pt/floater_windlight_options.xml
@@ -100,7 +100,7 @@
I
- Angulo Leste
+ Ângulo Leste
@@ -114,7 +114,7 @@
- Brilho da Estrêla
+ Brilho da Estrela
diff --git a/linden/indra/newview/skins/default/xui/pt/menu_viewer.xml b/linden/indra/newview/skins/default/xui/pt/menu_viewer.xml
index 56195b5..229f457 100644
--- a/linden/indra/newview/skins/default/xui/pt/menu_viewer.xml
+++ b/linden/indra/newview/skins/default/xui/pt/menu_viewer.xml
@@ -125,9 +125,9 @@
- Sélectionner l'option Publier dans la recherche
+ Sélectionner l'option Afficher dans la recherche
En cochant cette case, votre parcelle apparaîtra :
- dans les résultats de recherche ;
- dans les objets publics ;
@@ -4345,7 +4345,7 @@ La flèche jaune au dessus de la ligne du temps représente votre vue actuelle,
Vous pouvez définir la position d'une clé en la faisant glisser le long de la ligne du temps ou en définissant manuellement ses valeurs dans la section Réglages des images-clés. Dans cette même section, vous pouvez associer chaque clé au préréglage WindLight respectif.
-La Durée du cycle contrôle la durée d'une « journée ». Si vous choisissez une valeur basse (2mn par exemple), cela signigie que toutes les animations de votre journée de 24h se dérouleront en 2mn seulement ! Une fois satisfait de vos choix, utilisez les boutons Jouer et Stop ! pour prévisualiser les résultats. Pour voir l'animation, vous pouvez aussi bouger la flèche jaune au dessus de la ligne du temps. Si vous sélectionnez l'option Utiliser heure domaine, vous synchronisez la durée de votre journée et votre heure avec celle du cycle du jour du domaine.
+La Durée du cycle contrôle la durée d'une « journée ». Si vous choisissez une valeur basse (2mn par exemple), cela signifie que toutes les animations de votre journée de 24h se dérouleront en 2mn seulement ! Une fois satisfait de vos choix, utilisez les boutons Jouer et Stop ! pour prévisualiser les résultats. Pour voir l'animation, vous pouvez aussi bouger la flèche jaune au dessus de la ligne du temps. Si vous sélectionnez l'option Utiliser heure domaine, vous synchronisez la durée de votre journée et votre heure avec celle du cycle du jour du domaine.
Une fois que vous êtes satisfait de votre Cycle du jour, vous pouvez le sauvegarder et le charger grâce aux boutons Enregistrer jour test et Charger Jour Test. Veuillez noter que pour l'instant, nous ne pouvez avoir qu'un seul Cycle du jour.
@@ -4503,7 +4503,7 @@ Même chose que l'azimut.
- Contrôle la quantité de lumière réfractée lorsque quelque votre avatar regarde au dessus de l'eau.
+ Contrôle la quantité de lumière réfractée lorsque votre avatar regarde au dessus de l'eau.
diff --git a/linden/indra/newview/skins/default/xui/fr/floater_about.xml b/linden/indra/newview/skins/default/xui/fr/floater_about.xml
index b96aa70..22bc6e5 100644
--- a/linden/indra/newview/skins/default/xui/fr/floater_about.xml
+++ b/linden/indra/newview/skins/default/xui/fr/floater_about.xml
@@ -3,7 +3,7 @@
Second Life existe grâce aux efforts de Philip, Tessa, Andrew, Cory, James, Ben, Char, Charlie, Colin, Dan, Daniel, Doug, Eric, Hamlet, Haney, Eve, Hunter, Ian, Jeff, Jennifer, Jim, John, Lee, Mark, Peter, Phoenix, Richard, Robin, Xenon, Steve, Tanya, Eddie, Avi, Frank, Bruce, Aaron, Alice, Bob, Debra, Eileen, Helen, Janet, Louie, Leviathania, Stefan, Ray, Kevin, Tom, Mikeb, MikeT, Burgess, Elena, Tracy, Bill, Todd, Ryan, Zach, Sarah, Nova, Tim, Stephanie, Michael, Evan, Nicolas, Catherine, Rachelle, Dave, Holly, Bub, Kelly, Magellan, Ramzi, Don, Sabin, Jill, Rheya, Jeska, Torley, Kona, Callum, Charity, Ventrella, Jack, Vektor, Iris, Chris, Nicole, Mick, Reuben, Blue, Babbage, Yedwab, Deana, Lauren, Brent, Pathfinder, Chadrick, Altruima, Jesse, Teeny, Monroe, Icculus, David, Tess, Lizzie, Patsy, Isaac, Lawrence, Cyn, Bo, Gia, Annette, Marius, Tbone, Jonathan, Karen, Ginsu, Satoko, Yuko, Makiko, Thomas, Harry, Seth, Alexei, Brian, Guy, Runitai, Ethan, Data, Cornelius, Kenny, Swiss, Zero, Natria, Wendy, Stephen, Teeple, Thumper, Lucy, Dee, Mia, Liana, Warren, Branka, Aura, beez, Milo, Hermia, Red, Thrax, Joe, Sally, Magenta, Mogura, Paul, Jose, Rejean, Henrik, Lexie, Amber, Logan, Xan, Nora, Morpheus, Donovan, Leyla, MichaelFrancis, Beast, Cube, Bucky, Joshua, Stryfe, Harmony, Teresa, Claudia, Walker, Glenn, Fritz, Fordak, June, Cleopetra, Jean, Ivy, Betsy, Roosevelt, Spike, Ken, Which, Tofu, Chiyo, Rob, Zee, dustin, George, Del, Matthew, Cat, Jacqui, Lightfoot, Adrian, Viola, Alfred, Noel, Irfan, Sunil, Yool, Rika, Jane, Xtreme, Frontier, a2, Neo, Siobhan, Yoz, Justin, Elle, Qarl, Benjamin, Isabel, Gulliver, Everett, Christopher, Izzy, Stephany, Garry, Sejong, Sean, Tobin, Iridium, Meta, Anthony, Jeremy, JP, Jake, Maurice, Madhavi, Leopard, Kyle, Joon, Kari, Bert, Belinda, Jon, Kristi, Bridie, Pramod, KJ, Socrates, Maria, Ivan, Aric, Yamasaki, Adreanne, Jay, MitchK, Ceren, Coco, Durl, Jenny, Periapse, Kartic, Storrs, Lotte, Sandy, Rohn, Colossus, Zen, BigPapi, Brad, Pastrami, Kurz, Mani, Neuro, Jaime, MJ, Rowan, Sgt, Elvis, Gecko, Samuel, Sardonyx, Leo, Bryan, Niko, Soft, Poppy, Rachel, Aki, Angelo, Banzai, Alexa, Sue, CeeLo, Bender, CG, Gillian, Pelle, Nick, Echo, Zara, Christine, Shamiran, Emma, Blake, Keiko, Plexus, Joppa, Sidewinder, Erica, Ashlei, Twilight, Kristen, Brett, Q, Enus, Simon, Bevis, Kraft, Kip, Chandler, Ron, LauraP, Ram, KyleJM, Scouse, Prospero, Melissa, Marty, Nat, Hamilton, Kend, Lordan, Jimmy, Kosmo, Seraph, Green, Ekim, Wiggo, JT, Rome, Doris, Miz, Benoc, Whump, Trinity, Patch, Kate, TJ, Bao, Joohwan, Christy, Sofia, Matias, Cogsworth, Johan, Oreh, Cheah, Angela, Brandy, Mango, Lan, Aleks, Gloria, Heidy, Mitchell, Space, Colton, Bambers, Einstein, Maggie, Malbers, Rose, Winnie, Stella, Milton, Rothman, Niall, Marin, Allison, Katie, Dawn, Katt, Dusty, Kalpana, Judy, Andrea, Ambroff, Infinity, Gail, Rico, Raymond, Yi, William, Christa, M, Teagan, Scout, Molly, Dante, Corr, Dynamike, Usi, Kaylee, Vidtuts, Lil, Danica, Sascha, Kelv, Jacob, Nya, Rodney, Brandon, Elsie, Blondin, Grant, Katrin, Nyx, Gabriel, Locklainn, Claire, Devin, Minerva, Monty, Austin, Bradford, Si, Keira, H, Caitlin, Dita, Makai, Jenn, Ann, Meredith, Clare, Joy, Praveen, Cody, Edmund, Ruthe, Sirena, Gayathri, Spider, FJ, Davidoff, Tian, Jennie, Louise, Oskar, Landon, Noelle, Jarv, Ingrid, Al, Sommer, Doc, Aria, Huin, Gray, Lili, Vir, DJ, Yang, T, Simone, Maestro, Scott, Charlene, Quixote, Amanda, Susan, Zed, Anne, Enkidu, Esbee, Joroan, Katelin, Roxie, Tay, Scarlet, Kevin, Johnny, Wolfgang, Andren, Bob, Howard, Merov, Rand, Ray, Michon, Newell, Galen, Dessie, Les et de nombreuses autres personnes.
- Tous nos remerciements aux résidents suivants pour avoir testé cette version (la meilleure qui soit jusqu'à présent) : afon shepherd, Aimee Trescothick, Alexandrea Fride, Alissa Sabre, Amber DeCuir, Asuka Neely, Auron Forcella, Blue Revolution, Bocan Undercroft, Boroondas Gupte, Brandon Shinobu, Bri Gufler, Buckaroo Mu, Celierra Darling, Christos Atlantis, Coder Kas, Cummere Mayo, dakota schwade, Dirk Talamasca, Dizzy Banjo, Drew Dwi, Duckling Kwak, Ellla McMahon, Erikah Jameson, Erinyse Planer, Eyana Yohkoh, Ezian Ecksol, Faron Karu, Fenoe Lowey, Fox Hwasung, Francisca Biedermann, Gally Young, Gellan Glenelg, Geneko Nemeth, Glenn Rotaru, Hagar Qinan, Harleen Gretzky, Holger Gilruth, hotrodjohnny gears, IAm Zabelin, Inigo Catteneo, Iustinian Tomsen, Jacek Antonell, James Benedek, Jim Kupferberg, Joeseph Albanese, JPT62089 Agnon, Kardargo Adamczyk, Kirstenlee Cinquetti, Latif Khalifa, lea Parnall, Lex Neva, Lillith Anatine, Lilly Zenovka, Lim Catteneo, Lindal Kidd, Mark Rosenbaum, MasterJ Chaplin, McCabe Maxsted, Melvin Starbrook, Meni Kaiousei, Mero Collas, Minakothegothicgeisha Kamachi, Moon Metty, neofilo aabye, Neutron Chesnokov, Nomad Ingwer, norritt Xi, Opensource Obscure, Oracle Weatherwax, Ourasi Ferraris, Pabl0 Roffo, Peyton Aleixandre, Phli Foxchase, Psi Merlin, r2d2 Wunderlich, Regi Yifu, Saijanai Kuhn, Sandor Balczo, Sarkan Dreamscape, Scree Raymaker, Sedona Mills, Selena Beale, Sheet Spotter, Shibari Twine, Silver Key, Simon Kline, SLB Wirefly, Stacy Wombat, Sugarcult Dagger, Tayra Dagostino, Tetsuryu Vlodovic, ThaBiGGDoGG Richez, Timo Gufler, tx Oh, wayfinder wishbringer, Wizzytoe McCullough, Wundur Primbee, Yann Dufaux, Yuu Nakamichi
+ Tous nos remerciements aux résidents suivants pour avoir testé cette version (la meilleure qui soit jusqu'à présent) : afon shepherd, Aimee Trescothick, Alexandrea Fride, Alissa Sabre, Amber DeCuir, Asuka Neely, Auron Forcella, Blue Revolution, Bocan Undercroft, Boroondas Gupte, Brandon Shinobu, Bri Gufler, Buckaroo Mu, Celierra Darling, Christos Atlantis, Coder Kas, Cummere Mayo, dakota schwade, Dirk Talamasca, Dizzy Banjo, Drew Dwi, Duckling Kwak, Ellla McMahon, Erikah Jameson, Erinyse Planer, Eyana Yohkoh, Ezian Ecksol, Faron Karu, Fenoe Lowey, Fox Hwasung, Francisca Biedermann, Gally Young, Gellan Glenelg, Geneko Nemeth, Glenn Rotaru, Hagar Qinan, Harleen Gretzky, Holger Gilruth, hotrodjohnny gears, IAm Zabelin, Inigo Catteneo, Iustinian Tomsen, Jacek Antonell, James Benedek, Jim Kupferberg, Joeseph Albanese, JPT62089 Agnon, Kardargo Adamczyk, Kirstenlee Cinquetti, Latif Khalifa, lea Parnall, Lex Neva, Lillith Anatine, Lilly Zenovka, Lim Catteneo, Lindal Kidd, Mark Rosenbaum, MasterJ Chaplin, McCabe Maxsted, Melvin Starbrook, Meni Kaiousei, Mero Collas, Minakothegothicgeisha Kamachi, Moon Metty, neofilo aabye, Neutron Chesnokov, Nomad Ingwer, norritt Xi, Opensource Obscure, Oracle Weatherwax, Ourasi Ferraris, Pabl0 Roffo, Peyton Aleixandre, Phli Foxchase, Psi Merlin, r2d2 Wunderlich, Regi Yifu, Saijanai Kuhn, Sandor Balczo, Sarkan Dreamscape, Scree Raymaker, Sedona Mills, Selena Beale, Sheet Spotter, Shibari Twine, Silver Key, Simon Kline, SLB Wirefly, Stacy Wombat, Sugarcult Dagger, Tayra Dagostino, Tetsuryu Vlodovic, ThaBiGGDoGG Richez, Timo Gufler, tx Oh, wayfinder wishbringer, Wizzytoe McCullough, Wundur Primbee, Yann Dufaux, Yuu Nakamichi, Aminom Marvin, Andred Qinan, arminasx saiman, caroline apollo, Iskar Ariantho, Jenika Connolly, Maghnus Balogh, Nefertiti Nefarious, RodneyLee Jessop
3Dconnexion SDK Copyright (C) 1992-2007 3Dconnexion
APR Copyright (C) 2000-2004 The Apache Software Foundation
diff --git a/linden/indra/newview/skins/default/xui/fr/floater_about_land.xml b/linden/indra/newview/skins/default/xui/fr/floater_about_land.xml
index 03d6260..44b505b 100644
--- a/linden/indra/newview/skins/default/xui/fr/floater_about_land.xml
+++ b/linden/indra/newview/skins/default/xui/fr/floater_about_land.xml
@@ -160,7 +160,7 @@ Allez dans le menu Monde > À propos du terrain ou sélectionnez une autre pa
Facteur Bonus Objets : [BONUS]
- Prims utilisés sur la parcelle :
+ Prims utilisées sur la parcelle :
[COUNT] sur [MAX] ([AVAILABLE] disponibles)
@@ -319,13 +319,13 @@ Allez dans le menu Monde > À propos du terrain ou sélectionnez une autre pa
- Bloquée
+ Bloqué
- Lieu d'arrivée défini
+ Lieu d'arrivée fixe
- Lieu d'arrivée indéfini
+ Lieu d'arrivée libre
diff --git a/linden/indra/newview/skins/default/xui/fr/floater_animation_preview.xml b/linden/indra/newview/skins/default/xui/fr/floater_animation_preview.xml
index ceda06b..4af68a6 100644
--- a/linden/indra/newview/skins/default/xui/fr/floater_animation_preview.xml
+++ b/linden/indra/newview/skins/default/xui/fr/floater_animation_preview.xml
@@ -138,7 +138,7 @@
-
+
diff --git a/linden/indra/newview/skins/default/xui/fr/floater_avatar_textures.xml b/linden/indra/newview/skins/default/xui/fr/floater_avatar_textures.xml
index c1e324f..63f5ae8 100644
--- a/linden/indra/newview/skins/default/xui/fr/floater_avatar_textures.xml
+++ b/linden/indra/newview/skins/default/xui/fr/floater_avatar_textures.xml
@@ -1,4 +1,4 @@
-
+
Baked Textures
@@ -20,7 +20,7 @@
-
+
diff --git a/linden/indra/newview/skins/default/xui/fr/floater_buy_currency.xml b/linden/indra/newview/skins/default/xui/fr/floater_buy_currency.xml
index f6c7455..3268a42 100644
--- a/linden/indra/newview/skins/default/xui/fr/floater_buy_currency.xml
+++ b/linden/indra/newview/skins/default/xui/fr/floater_buy_currency.xml
@@ -1,7 +1,7 @@
-
+
- Achat de devises :
+ Achat de L$ :
Impossible d'acheter maintenant :
@@ -17,7 +17,7 @@
En train de contacter le Lindex...
- Acheter des L$ sur le marché des changes du LindeX
+ Acheter des L$ sur le marché du LindeX
[NAME] L$ [PRICE]
diff --git a/linden/indra/newview/skins/default/xui/fr/floater_customize.xml b/linden/indra/newview/skins/default/xui/fr/floater_customize.xml
index ddbcbbd..3482071 100644
--- a/linden/indra/newview/skins/default/xui/fr/floater_customize.xml
+++ b/linden/indra/newview/skins/default/xui/fr/floater_customize.xml
@@ -1,4 +1,4 @@
-
+
@@ -348,8 +348,8 @@ et la porter.
-
-
+
+
diff --git a/linden/indra/newview/skins/default/xui/fr/floater_inventory.xml b/linden/indra/newview/skins/default/xui/fr/floater_inventory.xml
index 4a552bd..ba545ec 100644
--- a/linden/indra/newview/skins/default/xui/fr/floater_inventory.xml
+++ b/linden/indra/newview/skins/default/xui/fr/floater_inventory.xml
@@ -29,7 +29,7 @@
-
+
- Employé(e) de Linden Lab
+ Employé de Linden Lab
Infos de paiement utilisées
@@ -28,10 +28,10 @@
Aucune info de paiement enregistrée
- Personne dont l'âge a été vérifié
+ Âge vérifié
- Personne dont l'âge n'a pas été vérifié
+ Âge non vérifié
Nom :
@@ -87,7 +87,7 @@ objets :
Dans un navigateur externe
- URL du domicile
+ URL de la page d'accueil
diff --git a/linden/indra/newview/skins/default/xui/fr/panel_media_controls.xml b/linden/indra/newview/skins/default/xui/fr/panel_media_controls.xml
index 6451b51..e177b33 100644
--- a/linden/indra/newview/skins/default/xui/fr/panel_media_controls.xml
+++ b/linden/indra/newview/skins/default/xui/fr/panel_media_controls.xml
@@ -1,12 +1,12 @@
-
-
-
+
+
+
-
-
-
+
+
+
@@ -18,7 +18,7 @@
Stop
- Pauser
+ Mettre en pause
Aucun média spécifié
@@ -33,6 +33,6 @@
Cet endroit propose du contenu vidéo. Cliquez sur Jouer pour lire la vidéo.
- Afficher le contenu web qui se trouve ici.
+ Affichez le contenu web.
diff --git a/linden/indra/newview/skins/default/xui/fr/panel_preferences_input.xml b/linden/indra/newview/skins/default/xui/fr/panel_preferences_input.xml
index c21ae24..da0a293 100644
--- a/linden/indra/newview/skins/default/xui/fr/panel_preferences_input.xml
+++ b/linden/indra/newview/skins/default/xui/fr/panel_preferences_input.xml
@@ -1,4 +1,4 @@
-
+
Vue subjective :
@@ -27,7 +27,7 @@
Effet de lissage :
-
+
Affichage de l'avatar :
diff --git a/linden/indra/newview/skins/default/xui/fr/panel_preferences_network.xml b/linden/indra/newview/skins/default/xui/fr/panel_preferences_network.xml
index 32a9694..6035552 100644
--- a/linden/indra/newview/skins/default/xui/fr/panel_preferences_network.xml
+++ b/linden/indra/newview/skins/default/xui/fr/panel_preferences_network.xml
@@ -16,8 +16,8 @@
Emplacement du cache :
-
-
+
+
diff --git a/linden/indra/newview/skins/default/xui/ja/floater_about.xml b/linden/indra/newview/skins/default/xui/ja/floater_about.xml
index 10a1d89..c8f6b76 100644
--- a/linden/indra/newview/skins/default/xui/ja/floater_about.xml
+++ b/linden/indra/newview/skins/default/xui/ja/floater_about.xml
@@ -3,7 +3,7 @@
Second Lifeは、 Philip、Tessa、Andrew、Cory、James、Ben、Char、Charlie、Colin、Dan、Daniel、Doug、Eric、Hamlet、Haney、Eve、Hunter、Ian、Jeff、Jennifer、Jim、John、Lee、Mark、Peter、Phoenix、Richard、Robin、Xenon、Steve、Tanya、Eddie、Avi、Frank、Bruce、Aaron、Alice、Bob、Debra、Eileen、Helen、Janet、Louie、Leviathania、Stefan、Ray、Kevin、Tom、Mikeb、MikeT、Burgess、Elena、Tracy、Bill、Todd、Ryan、Zach、Sarah、Nova、Tim、Stephanie、Michael、Evan、Nicolas、Catherine、Rachelle、Dave、Holly、Bub、Kelly、Magellan、Ramzi、Don、Sabin、Jill、Rheya、Jeska、Torley、Kona、Callum、Charity、Ventrella、Jack、Vektor、Iris、Chris、Nicole、Mick、Reuben、Blue、Babbage、Yedwab、Deana、Lauren、Brent、Pathfinder、Chadrick、Altruima、Jesse、Teeny、Monroe、Icculus、David、Tess、Lizzie、Patsy、Isaac、Lawrence、Cyn、Bo、Gia、Annette、Marius、Tbone、Jonathan、Karen、Ginsu、Satoko、Yuko、Makiko、Thomas、Harry、Seth、Alexei、Brian、Guy、Runitai、Ethan、Data、Cornelius、Kenny、Swiss、Zero、Natria、Wendy、Stephen、Teeple、Thumper、Lucy、Dee、Mia、Liana、Warren、Branka、Aura、beez、Milo、Hermia、Red、Thrax、Joe、Sally、Magenta、Mogura、Paul、Jose、Rejean、Henrik、Lexie、Amber、Logan、Xan、Nora、Morpheus、Donovan、Leyla、MichaelFrancis、Beast、Cube、Bucky、Joshua、Stryfe、Harmony、Teresa、Claudia、Walker、Glenn、Fritz、Fordak、June、Cleopetra、Jean、Ivy、Betsy、Roosevelt、Spike、Ken、Which、Tofu、Chiyo、Rob、Zee、dustin、George、Del、Matthew、Cat、Jacqui、Lightfoot、Adrian、Viola、Alfred、Noel、Irfan、Sunil、Yool、Rika、Jane、Xtreme、Frontier、a2、Neo、Siobhan、Yoz、Justin、Elle、Qarl、Benjamin、Isabel、Gulliver、Everett、Christopher、Izzy、Stephany、Garry、Sejong、Sean、Tobin、Iridium、Meta、Anthony、Jeremy、JP、Jake、Maurice、Madhavi、Leopard、Kyle、Joon、Kari、Bert、Belinda、Jon、Kristi、Bridie、Pramod、KJ、Socrates、Maria、Ivan、Aric、Yamasaki、Adreanne、Jay、MitchK、Ceren、Coco、Durl、Jenny、Periapse、Kartic、Storrs、Lotte、Sandy、Rohn、Colossus、Zen、BigPapi、Brad、Pastrami、Kurz、Mani、Neuro、Jaime、MJ、Rowan、Sgt、Elvis、Gecko、Samuel、Sardonyx、Leo、Bryan、Niko、Soft、Poppy、Rachel、Aki、Angelo、Banzai、Alexa、Sue、CeeLo、Bender、CG、Gillian、Pelle、Nick、Echo、Zara、Christine、Shamiran、Emma、Blake、Keiko、Plexus、Joppa、Sidewinder、Erica、Ashlei、Twilight、Kristen、Brett、Q、Enus、Simon、Bevis、Kraft、Kip、Chandler、Ron、LauraP、Ram、KyleJM、Scouse、Prospero、Melissa、Marty、Nat、Hamilton、Kend、Lordan、Jimmy、Kosmo、Seraph、Green、Ekim、Wiggo、JT、Rome、Doris、Miz、Benoc、Whump、Trinity、Patch、Kate、TJ、Bao、Joohwan、Christy、Sofia、Matias、Cogsworth、Johan、Oreh、Cheah、Angela、Brandy、Mango、Lan、Aleks、Gloria、Heidy、Mitchell、Space、Colton、Bambers、Einstein、Maggie、Malbers、Rose、Winnie、Stella、Milton、Rothman、Niall、Marin、Allison、Katie、Dawn、Katt、Dusty、Kalpana、Judy、Andrea、Ambroff、Infinity、Gail、Rico、Raymond、Yi、William、Christa、M、Teagan、Scout、Molly、Dante、Corr、Dynamike、Usi、Kaylee、Vidtuts、Lil、Danica、Sascha、Kelv、Jacob、Nya、Rodney、Brandon、Elsie、Blondin、Grant、Katrin、Nyx、Gabriel、Locklainn、Claire、Devin、Minerva、Monty、Austin、Bradford、Si、Keira、H、Caitlin、Dita、Makai、Jenn、Ann、Meredith、Clare、Joy、Praveen、Cody、Edmund、Ruthe、Sirena、Gayathri、Spider、FJ、Davidoff、Tian、Jennie、Louise、Oskar、Landon、Noelle、Jarv、Ingrid、Al、Sommer、Doc、Aria、Huin、Gray、Lili、Vir、DJ、Yang、T、Simone、Maestro、Scott、Charlene、Quixote、Amanda、Susan、Zed、Anne、Enkidu、Esbee、Joroan、Katelin、Roxie、Tay、Scarlet、Kevin、Johnny、Wolfgang、Andren、Bob、Howard、Merov、Rand、Ray、Michon、Newell、Galen、Dessie、Lesと、その他多数の人達によって作成されました。
- このバージョンをこれまでで最高のものになるようご協力をいただいた以下の住人の皆様に深く感謝いたします。 afon shepherd、Aimee Trescothick、Alexandrea Fride、Alissa Sabre、Amber DeCuir、Asuka Neely、Auron Forcella、Blue Revolution、Bocan Undercroft、Boroondas Gupte、Brandon Shinobu、Bri Gufler、Buckaroo Mu、Celierra Darling、Christos Atlantis、Coder Kas、Cummere Mayo、dakota schwade、Dirk Talamasca、Dizzy Banjo、Drew Dwi、Duckling Kwak、Ellla McMahon、Erikah Jameson、Erinyse Planer、Eyana Yohkoh、Ezian Ecksol、Faron Karu、Fenoe Lowey、Fox Hwasung、Francisca Biedermann、Gally Young、Gellan Glenelg、Geneko Nemeth、Glenn Rotaru、Hagar Qinan、Harleen Gretzky、Holger Gilruth、hotrodjohnny gears、IAm Zabelin、Inigo Catteneo、Iustinian Tomsen、Jacek Antonell、James Benedek、Jim Kupferberg、Joeseph Albanese、JPT62089 Agnon、Kardargo Adamczyk、Kirstenlee Cinquetti、Latif Khalifa、lea Parnall、Lex Neva、Lillith Anatine、Lilly Zenovka、Lim Catteneo、Lindal Kidd、Mark Rosenbaum、MasterJ Chaplin、McCabe Maxsted、Melvin Starbrook、Meni Kaiousei、Mero Collas、Minakothegothicgeisha Kamachi、Moon Metty、neofilo aabye、Neutron Chesnokov、Nomad Ingwer、norritt Xi、Opensource Obscure、Oracle Weatherwax、Ourasi Ferraris、Pabl0 Roffo、Peyton Aleixandre、Phli Foxchase、Psi Merlin、r2d2 Wunderlich、Regi Yifu、Saijanai Kuhn、Sandor Balczo、Sarkan Dreamscape、Scree Raymaker、Sedona Mills、Selena Beale、Sheet Spotter、Shibari Twine、Silver Key、Simon Kline、SLB Wirefly、Stacy Wombat、Sugarcult Dagger、Tayra Dagostino、Tetsuryu Vlodovic、ThaBiGGDoGG Richez、Timo Gufler、tx Oh、wayfinder wishbringer、Wizzytoe McCullough、Wundur Primbee、Yann Dufaux、Yuu Nakamichi
+ このバージョンをこれまでで最高のものになるようご協力をいただいた以下の住人の皆様に深く感謝いたします。 afon shepherd、Aimee Trescothick、Alexandrea Fride、Alissa Sabre、Amber DeCuir、Asuka Neely、Auron Forcella、Blue Revolution、Bocan Undercroft、Boroondas Gupte、Brandon Shinobu、Bri Gufler、Buckaroo Mu、Celierra Darling、Christos Atlantis、Coder Kas、Cummere Mayo、dakota schwade、Dirk Talamasca、Dizzy Banjo、Drew Dwi、Duckling Kwak、Ellla McMahon、Erikah Jameson、Erinyse Planer、Eyana Yohkoh、Ezian Ecksol、Faron Karu、Fenoe Lowey、Fox Hwasung、Francisca Biedermann、Gally Young、Gellan Glenelg、Geneko Nemeth、Glenn Rotaru、Hagar Qinan、Harleen Gretzky、Holger Gilruth、hotrodjohnny gears、IAm Zabelin、Inigo Catteneo、Iustinian Tomsen、Jacek Antonell、James Benedek、Jim Kupferberg、Joeseph Albanese、JPT62089 Agnon、Kardargo Adamczyk、Kirstenlee Cinquetti、Latif Khalifa、lea Parnall、Lex Neva、Lillith Anatine、Lilly Zenovka、Lim Catteneo、Lindal Kidd、Mark Rosenbaum、MasterJ Chaplin、McCabe Maxsted、Melvin Starbrook、Meni Kaiousei、Mero Collas、Minakothegothicgeisha Kamachi、Moon Metty、neofilo aabye、Neutron Chesnokov、Nomad Ingwer、norritt Xi、Opensource Obscure、Oracle Weatherwax、Ourasi Ferraris、Pabl0 Roffo、Peyton Aleixandre、Phli Foxchase、Psi Merlin、r2d2 Wunderlich、Regi Yifu、Saijanai Kuhn、Sandor Balczo、Sarkan Dreamscape、Scree Raymaker、Sedona Mills、Selena Beale、Sheet Spotter、Shibari Twine、Silver Key、Simon Kline、SLB Wirefly、Stacy Wombat、Sugarcult Dagger、Tayra Dagostino、Tetsuryu Vlodovic、ThaBiGGDoGG Richez、Timo Gufler、tx Oh、wayfinder wishbringer、Wizzytoe McCullough、Wundur Primbee、Yann Dufaux、Yuu Nakamichi、Aminom Marvin、Andred Qinan、arminasx saiman、caroline apollo、Iskar Ariantho、Jenika Connolly、Maghnus Balogh、Nefertiti Nefarious、RodneyLee Jessop
3Dconnexion SDK Copyright (C) 1992-2007 3Dconnexion
APR Copyright (C) 2000-2004 The Apache Software Foundation
diff --git a/linden/indra/newview/skins/default/xui/ja/floater_about_land.xml b/linden/indra/newview/skins/default/xui/ja/floater_about_land.xml
index 3b8aee6..c1d944b 100644
--- a/linden/indra/newview/skins/default/xui/ja/floater_about_land.xml
+++ b/linden/indra/newview/skins/default/xui/ja/floater_about_land.xml
@@ -63,7 +63,7 @@
-
+
@@ -211,7 +211,7 @@
[COUNT]
- 他の住人のオブジェクトの自動返却(分、オフの設定は0)
+ 他の住人のオブジェクトの自動返却(分、0で自動返却なし)
@@ -254,7 +254,7 @@
-
+
全カテゴリー
@@ -311,11 +311,11 @@
- テレポート・ルート:
+ テレポート制限:
- ブロック済み
+ 不可
着地点
@@ -399,13 +399,13 @@
次の住人のアクセスをブロック:
-
+
1つ以上のオプションが、不動産レベルで設定されています。
-
+
誰でも
@@ -414,7 +414,7 @@
グループ
-
+
許可された住人
diff --git a/linden/indra/newview/skins/default/xui/ja/floater_bumps.xml b/linden/indra/newview/skins/default/xui/ja/floater_bumps.xml
index 89748b4..cbd6a2f 100644
--- a/linden/indra/newview/skins/default/xui/ja/floater_bumps.xml
+++ b/linden/indra/newview/skins/default/xui/ja/floater_bumps.xml
@@ -1,5 +1,5 @@
-
+
検出なし
diff --git a/linden/indra/newview/skins/default/xui/ja/floater_group_info.xml b/linden/indra/newview/skins/default/xui/ja/floater_group_info.xml
index 9330ca0..6b4ea3c 100644
--- a/linden/indra/newview/skins/default/xui/ja/floater_group_info.xml
+++ b/linden/indra/newview/skins/default/xui/ja/floater_group_info.xml
@@ -27,7 +27,7 @@
創設者:
- 特権:
+ 理念:
diff --git a/linden/indra/newview/skins/default/xui/ja/floater_inventory_item_properties.xml b/linden/indra/newview/skins/default/xui/ja/floater_inventory_item_properties.xml
index be2710c..9a408b9 100644
--- a/linden/indra/newview/skins/default/xui/ja/floater_inventory_item_properties.xml
+++ b/linden/indra/newview/skins/default/xui/ja/floater_inventory_item_properties.xml
@@ -12,14 +12,14 @@
Nicole Linden
-
+
オーナー:
Thrax Linden
-
+
入手日時:
diff --git a/linden/indra/newview/skins/default/xui/ja/floater_live_lsleditor.xml b/linden/indra/newview/skins/default/xui/ja/floater_live_lsleditor.xml
index d28c673..7994273 100644
--- a/linden/indra/newview/skins/default/xui/ja/floater_live_lsleditor.xml
+++ b/linden/indra/newview/skins/default/xui/ja/floater_live_lsleditor.xml
@@ -2,7 +2,7 @@
-
+
あなたはこのスクリプトを見ることができません。
diff --git a/linden/indra/newview/skins/default/xui/ja/floater_lsl_guide.xml b/linden/indra/newview/skins/default/xui/ja/floater_lsl_guide.xml
index 238070b..dbfce5d 100644
--- a/linden/indra/newview/skins/default/xui/ja/floater_lsl_guide.xml
+++ b/linden/indra/newview/skins/default/xui/ja/floater_lsl_guide.xml
@@ -3,5 +3,5 @@
-
+
diff --git a/linden/indra/newview/skins/default/xui/ja/floater_media_browser.xml b/linden/indra/newview/skins/default/xui/ja/floater_media_browser.xml
index 3870ae1..be056e2 100644
--- a/linden/indra/newview/skins/default/xui/ja/floater_media_browser.xml
+++ b/linden/indra/newview/skins/default/xui/ja/floater_media_browser.xml
@@ -1,5 +1,5 @@
-
+
@@ -11,8 +11,8 @@
-
-
+
+
diff --git a/linden/indra/newview/skins/default/xui/ja/floater_tools.xml b/linden/indra/newview/skins/default/xui/ja/floater_tools.xml
index 0842e81..d88f0b8 100644
--- a/linden/indra/newview/skins/default/xui/ja/floater_tools.xml
+++ b/linden/indra/newview/skins/default/xui/ja/floater_tools.xml
@@ -582,11 +582,11 @@
-
+
メディア・テクスチャーを揃える
(最初にロードすること)
-
+
-
+
@@ -89,7 +89,7 @@
-
+
@@ -97,7 +97,7 @@
-
+
@@ -149,20 +149,20 @@
-
-
+
+
-
+
-
+
-
+
-
+
@@ -170,13 +170,13 @@
-
-
+
+
-
-
-
+
+
+
@@ -187,7 +187,7 @@
-
+
@@ -198,7 +198,7 @@
-
+
diff --git a/linden/indra/newview/skins/default/xui/ja/panel_group_general.xml b/linden/indra/newview/skins/default/xui/ja/panel_group_general.xml
index 382e85e..db81e18 100644
--- a/linden/indra/newview/skins/default/xui/ja/panel_group_general.xml
+++ b/linden/indra/newview/skins/default/xui/ja/panel_group_general.xml
@@ -22,16 +22,16 @@
(待機中)
- グループ憲章
+ グループ理念
- グループ憲章
+ グループの理念、指針を記入してください
- オーナーと可視メンバー
+ オーナーと公開メンバー
(オーナーは太字で表示されています)
diff --git a/linden/indra/newview/skins/default/xui/ja/panel_group_roles.xml b/linden/indra/newview/skins/default/xui/ja/panel_group_roles.xml
index 5ed3bf7..79f4e74 100644
--- a/linden/indra/newview/skins/default/xui/ja/panel_group_roles.xml
+++ b/linden/indra/newview/skins/default/xui/ja/panel_group_roles.xml
@@ -24,7 +24,7 @@
下にある役割を選択してください。 役割の名前、説明、メンバー・タイトルを修正することができます。
- 以下の役割を選択し、役割のプロパティ、メンバー、および許可された能力を確
+ 役割名を選択すると、説明、メンバー、能力を確認することができます。
あなたは役割に能力を割り当てることもできます。
@@ -133,7 +133,7 @@ Ctrl キーを押しながらメンバー名をクリックすると
許可された能力
-
+
@@ -154,4 +154,4 @@ Ctrl キーを押しながらメンバー名をクリックすると
能力のあるメンバー
-
\ No newline at end of file
+
diff --git a/linden/indra/newview/skins/default/xui/ja/panel_preferences_im.xml b/linden/indra/newview/skins/default/xui/ja/panel_preferences_im.xml
index e9044e2..16e11ab 100644
--- a/linden/indra/newview/skins/default/xui/ja/panel_preferences_im.xml
+++ b/linden/indra/newview/skins/default/xui/ja/panel_preferences_im.xml
@@ -26,6 +26,7 @@
- 取り込み中応答メッセージ:
+ 取り込み中
+応答メッセージ:
diff --git a/linden/indra/newview/skins/default/xui/ja/panel_preferences_web.xml b/linden/indra/newview/skins/default/xui/ja/panel_preferences_web.xml
index c1115a7..01a8dc9 100644
--- a/linden/indra/newview/skins/default/xui/ja/panel_preferences_web.xml
+++ b/linden/indra/newview/skins/default/xui/ja/panel_preferences_web.xml
@@ -3,11 +3,11 @@
- 外部のウェブ・ブラウザー (Firefox, Safari, Internet Explorer) を使用
+ 外部のウェブ・ブラウザ (Firefox, Safari, Internet Explorer) を使用
- 内蔵のウェブ・ブラウザーを使用
+ 内蔵のウェブ・ブラウザを使用
diff --git a/linden/indra/newview/skins/default/xui/ja/panel_region_estate.xml b/linden/indra/newview/skins/default/xui/ja/panel_region_estate.xml
index 67f05eb..186820f 100644
--- a/linden/indra/newview/skins/default/xui/ja/panel_region_estate.xml
+++ b/linden/indra/newview/skins/default/xui/ja/panel_region_estate.xml
@@ -20,7 +20,7 @@
次へのアクセスを制限:
+ tool_tip="支払い情報未登録の住人を排除する" />
diff --git a/linden/indra/newview/skins/default/xui/ja/role_actions.xml b/linden/indra/newview/skins/default/xui/ja/role_actions.xml
index 2ad6607..57e6d62 100644
--- a/linden/indra/newview/skins/default/xui/ja/role_actions.xml
+++ b/linden/indra/newview/skins/default/xui/ja/role_actions.xml
@@ -39,11 +39,11 @@
name="role change actions" value="10" />
세컨드라이프 제작자들: Philip, Tessa, Andrew, Cory, James, Ben, Char, Charlie, Colin, Dan, Daniel, Doug, Eric, Hamlet, Haney, Eve, Hunter, Ian, Jeff, Jennifer, Jim, John, Lee, Mark, Peter, Phoenix, Richard, Robin, Xenon, Steve, Tanya, Eddie, Avi, Frank, Bruce, Aaron, Alice, Bob, Debra, Eileen, Helen, Janet, Louie, Leviathania, Stefan, Ray, Kevin, Tom, Mikeb, MikeT, Burgess, Elena, Tracy, Bill, Todd, Ryan, Zach, Sarah, Nova, Tim, Stephanie, Michael, Evan, Nicolas, Catherine, Rachelle, Dave, Holly, Bub, Kelly, Magellan, Ramzi, Don, Sabin, Jill, Rheya, Jeska, Torley, Kona, Callum, Charity, Ventrella, Jack, Vektor, Iris, Chris, Nicole, Mick, Reuben, Blue, Babbage, Yedwab, Deana, Lauren, Brent, Pathfinder, Chadrick, Altruima, Jesse, Teeny, Monroe, Icculus, David, Tess, Lizzie, Patsy, Isaac, Lawrence, Cyn, Bo, Gia, Annette, Marius, Tbone, Jonathan, Karen, Ginsu, Satoko, Yuko, Makiko, Thomas, Harry, Seth, Alexei, Brian, Guy, Runitai, Ethan, Data, Cornelius, Kenny, Swiss, Zero, Natria, Wendy, Stephen, Teeple, Thumper, Lucy, Dee, Mia, Liana, Warren, Branka, Aura, beez, Milo, Hermia, Red, Thrax, Joe, Sally, Magenta, Mogura, Paul, Jose, Rejean, Henrik, Lexie, Amber, Logan, Xan, Nora, Morpheus, Donovan, Leyla, MichaelFrancis, Beast, Cube, Bucky, Joshua, Stryfe, Harmony, Teresa, Claudia, Walker, Glenn, Fritz, Fordak, June, Cleopetra, Jean, Ivy, Betsy, Roosevelt, Spike, Ken, Which, Tofu, Chiyo, Rob, Zee, dustin, George, Del, Matthew, Cat, Jacqui, Lightfoot, Adrian, Viola, Alfred, Noel, Irfan, Sunil, Yool, Rika, Jane, Xtreme, Frontier, a2, Neo, Siobhan, Yoz, Justin, Elle, Qarl, Benjamin, Isabel, Gulliver, Everett, Christopher, Izzy, Stephany, Garry, Sejong, Sean, Tobin, Iridium, Meta, Anthony, Jeremy, JP, Jake, Maurice, Madhavi, Leopard, Kyle, Joon, Kari, Bert, Belinda, Jon, Kristi, Bridie, Pramod, KJ, Socrates, Maria, Ivan, Aric, Yamasaki, Adreanne, Jay, MitchK, Ceren, Coco, Durl, Jenny, Periapse, Kartic, Storrs, Lotte, Sandy, Rohn, Colossus, Zen, BigPapi, Brad, Pastrami, Kurz, Mani, Neuro, Jaime, MJ, Rowan, Sgt, Elvis, Gecko, Samuel, Sardonyx, Leo, Bryan, Niko, Soft, Poppy, Rachel, Aki, Angelo, Banzai, Alexa, Sue, CeeLo, Bender, CG, Gillian, Pelle, Nick, Echo, Zara, Christine, Shamiran, Emma, Blake, Keiko, Plexus, Joppa, Sidewinder, Erica, Ashlei, Twilight, Kristen, Brett, Q, Enus, Simon, Bevis, Kraft, Kip, Chandler, Ron, LauraP, Ram, KyleJM, Scouse, Prospero, Melissa, Marty, Nat, Hamilton, Kend, Lordan, Jimmy, Kosmo, Seraph, Green, Ekim, Wiggo, JT, Rome, Doris, Miz, Benoc, Whump, Trinity, Patch, Kate, TJ, Bao, Joohwan, Christy, Sofia, Matias, Cogsworth, Johan, Oreh, Cheah, Angela, Brandy, Mango, Lan, Aleks, Gloria, Heidy, Mitchell, Space, Colton, Bambers, Einstein, Maggie, Malbers, Rose, Winnie, Stella, Milton, Rothman, Niall, Marin, Allison, Katie, Dawn, Katt, Dusty, Kalpana, Judy, Andrea, Ambroff, Infinity, Gail, Rico, Raymond, Yi, William, Christa, M, Teagan, Scout, Molly, Dante, Corr, Dynamike, Usi, Kaylee, Vidtuts, Lil, Danica, Sascha, Kelv, Jacob, Nya, Rodney, Brandon, Elsie, Blondin, Grant, Katrin, Nyx, Gabriel, Locklainn, Claire, Devin, Minerva, Monty, Austin, Bradford, Si, Keira, H, Caitlin, Dita, Makai, Jenn, Ann, Meredith, Clare, Joy, Praveen, Cody, Edmund, Ruthe, Sirena, Gayathri, Spider, FJ, Davidoff, Tian, Jennie, Louise, Oskar, Landon, Noelle, Jarv, Ingrid, Al, Sommer, Doc, Aria, Huin, Gray, Lili, Vir, DJ, Yang, T, Simone, Maestro, Scott, Charlene, Quixote, Amanda, Susan, Zed, Anne, Enkidu, Esbee, Joroan, Katelin, Roxie, Tay, Scarlet, Kevin, Johnny, Wolfgang, Andren, Bob, Howard, Merov, Rand, Ray, Michon, Newell, Galen, Dessie, Les, 그 외에 많은 분들이 수고해 주셨습니다.
- 현재 버전이 최상의 버전이 될 수 있도록 도와주신 여러분께 감사드립니다: afon shepherd, Aimee Trescothick, Alexandrea Fride, Alissa Sabre, Amber DeCuir, Asuka Neely, Auron Forcella, Blue Revolution, Bocan Undercroft, Boroondas Gupte, Brandon Shinobu, Bri Gufler, Buckaroo Mu, Celierra Darling, Christos Atlantis, Coder Kas, Cummere Mayo, dakota schwade, Dirk Talamasca, Dizzy Banjo, Drew Dwi, Duckling Kwak, Ellla McMahon, Erikah Jameson, Erinyse Planer, Eyana Yohkoh, Ezian Ecksol, Faron Karu, Fenoe Lowey, Fox Hwasung, Francisca Biedermann, Gally Young, Gellan Glenelg, Geneko Nemeth, Glenn Rotaru, Hagar Qinan, Harleen Gretzky, Holger Gilruth, hotrodjohnny gears, IAm Zabelin, Inigo Catteneo, Iustinian Tomsen, Jacek Antonell, James Benedek, Jim Kupferberg, Joeseph Albanese, JPT62089 Agnon, Kardargo Adamczyk, Kirstenlee Cinquetti, Latif Khalifa, lea Parnall, Lex Neva, Lillith Anatine, Lilly Zenovka, Lim Catteneo, Lindal Kidd, Mark Rosenbaum, MasterJ Chaplin, McCabe Maxsted, Melvin Starbrook, Meni Kaiousei, Mero Collas, Minakothegothicgeisha Kamachi, Moon Metty, neofilo aabye, Neutron Chesnokov, Nomad Ingwer, norritt Xi, Opensource Obscure, Oracle Weatherwax, Ourasi Ferraris, Pabl0 Roffo, Peyton Aleixandre, Phli Foxchase, Psi Merlin, r2d2 Wunderlich, Regi Yifu, Saijanai Kuhn, Sandor Balczo, Sarkan Dreamscape, Scree Raymaker, Sedona Mills, Selena Beale, Sheet Spotter, Shibari Twine, Silver Key, Simon Kline, SLB Wirefly, Stacy Wombat, Sugarcult Dagger, Tayra Dagostino, Tetsuryu Vlodovic, ThaBiGGDoGG Richez, Timo Gufler, tx Oh, wayfinder wishbringer, Wizzytoe McCullough, Wundur Primbee, Yann Dufaux, Yuu Nakamichi
+ 현재 버전이 최상의 버전이 될 수 있도록 도와주신 여러분께 감사드립니다: afon shepherd, Aimee Trescothick, Alexandrea Fride, Alissa Sabre, Amber DeCuir, Asuka Neely, Auron Forcella, Blue Revolution, Bocan Undercroft, Boroondas Gupte, Brandon Shinobu, Bri Gufler, Buckaroo Mu, Celierra Darling, Christos Atlantis, Coder Kas, Cummere Mayo, dakota schwade, Dirk Talamasca, Dizzy Banjo, Drew Dwi, Duckling Kwak, Ellla McMahon, Erikah Jameson, Erinyse Planer, Eyana Yohkoh, Ezian Ecksol, Faron Karu, Fenoe Lowey, Fox Hwasung, Francisca Biedermann, Gally Young, Gellan Glenelg, Geneko Nemeth, Glenn Rotaru, Hagar Qinan, Harleen Gretzky, Holger Gilruth, hotrodjohnny gears, IAm Zabelin, Inigo Catteneo, Iustinian Tomsen, Jacek Antonell, James Benedek, Jim Kupferberg, Joeseph Albanese, JPT62089 Agnon, Kardargo Adamczyk, Kirstenlee Cinquetti, Latif Khalifa, lea Parnall, Lex Neva, Lillith Anatine, Lilly Zenovka, Lim Catteneo, Lindal Kidd, Mark Rosenbaum, MasterJ Chaplin, McCabe Maxsted, Melvin Starbrook, Meni Kaiousei, Mero Collas, Minakothegothicgeisha Kamachi, Moon Metty, neofilo aabye, Neutron Chesnokov, Nomad Ingwer, norritt Xi, Opensource Obscure, Oracle Weatherwax, Ourasi Ferraris, Pabl0 Roffo, Peyton Aleixandre, Phli Foxchase, Psi Merlin, r2d2 Wunderlich, Regi Yifu, Saijanai Kuhn, Sandor Balczo, Sarkan Dreamscape, Scree Raymaker, Sedona Mills, Selena Beale, Sheet Spotter, Shibari Twine, Silver Key, Simon Kline, SLB Wirefly, Stacy Wombat, Sugarcult Dagger, Tayra Dagostino, Tetsuryu Vlodovic, ThaBiGGDoGG Richez, Timo Gufler, tx Oh, wayfinder wishbringer, Wizzytoe McCullough, Wundur Primbee, Yann Dufaux, Yuu Nakamichi, Aminom Marvin, Andred Qinan, arminasx saiman, caroline apollo, Iskar Ariantho, Jenika Connolly, Maghnus Balogh, Nefertiti Nefarious, RodneyLee Jessop
3Dconnexion SDK Copyright (C) 1992-2007 3Dconnexion
APR Copyright (C) 2000-2004 The Apache Software Foundation
diff --git a/linden/indra/newview/skins/default/xui/zh/floater_about.xml b/linden/indra/newview/skins/default/xui/zh/floater_about.xml
index 8147d89..621195d 100644
--- a/linden/indra/newview/skins/default/xui/zh/floater_about.xml
+++ b/linden/indra/newview/skins/default/xui/zh/floater_about.xml
@@ -3,7 +3,7 @@
第二人生的推出应感谢以下人物的贡献: Philip, Tessa, Andrew, Cory, James, Ben, Char, Charlie, Colin, Dan, Daniel, Doug, Eric, Hamlet, Haney, Eve, Hunter, Ian, Jeff, Jennifer, Jim, John, Lee, Mark, Peter, Phoenix, Richard, Robin, Xenon, Steve, Tanya, Eddie, Avi, Frank, Bruce, Aaron, Alice, Bob, Debra, Eileen, Helen, Janet, Louie, Leviathania, Stefan, Ray, Kevin, Tom, Mikeb, MikeT, Burgess, Elena, Tracy, Bill, Todd, Ryan, Zach, Sarah, Nova, Tim, Stephanie, Michael, Evan, Nicolas, Catherine, Rachelle, Dave, Holly, Bub, Kelly, Magellan, Ramzi, Don, Sabin, Jill, Rheya, Jeska, Torley, Kona, Callum, Charity, Ventrella, Jack, Vektor, Iris, Chris, Nicole, Mick, Reuben, Blue, Babbage, Yedwab, Deana, Lauren, Brent, Pathfinder, Chadrick, Altruima, Jesse, Teeny, Monroe, Icculus, David, Tess, Lizzie, Patsy, Isaac, Lawrence, Cyn, Bo, Gia, Annette, Marius, Tbone, Jonathan, Karen, Ginsu, Satoko, Yuko, Makiko, Thomas, Harry, Seth, Alexei, Brian, Guy, Runitai, Ethan, Data, Cornelius, Kenny, Swiss, Zero, Natria, Wendy, Stephen, Teeple, Thumper, Lucy, Dee, Mia, Liana, Warren, Branka, Aura, beez, Milo, Hermia, Red, Thrax, Joe, Sally, Magenta, Mogura, Paul, Jose, Rejean, Henrik, Lexie, Amber, Logan, Xan, Nora, Morpheus, Donovan, Leyla, MichaelFrancis, Beast, Cube, Bucky, Joshua, Stryfe, Harmony, Teresa, Claudia, Walker, Glenn, Fritz, Fordak, June, Cleopetra, Jean, Ivy, Betsy, Roosevelt, Spike, Ken, Which, Tofu, Chiyo, Rob, Zee, dustin, George, Del, Matthew, Cat, Jacqui, Lightfoot, Adrian, Viola, Alfred, Noel, Irfan, Sunil, Yool, Rika, Jane, Xtreme, Frontier, a2, Neo, Siobhan, Yoz, Justin, Elle, Qarl, Benjamin, Isabel, Gulliver, Everett, Christopher, Izzy, Stephany, Garry, Sejong, Sean, Tobin, Iridium, Meta, Anthony, Jeremy, JP, Jake, Maurice, Madhavi, Leopard, Kyle, Joon, Kari, Bert, Belinda, Jon, Kristi, Bridie, Pramod, KJ, Socrates, Maria, Ivan, Aric, Yamasaki, Adreanne, Jay, MitchK, Ceren, Coco, Durl, Jenny, Periapse, Kartic, Storrs, Lotte, Sandy, Rohn, Colossus, Zen, BigPapi, Brad, Pastrami, Kurz, Mani, Neuro, Jaime, MJ, Rowan, Sgt, Elvis, Gecko, Samuel, Sardonyx, Leo, Bryan, Niko, Soft, Poppy, Rachel, Aki, Angelo, Banzai, Alexa, Sue, CeeLo, Bender, CG, Gillian, Pelle, Nick, Echo, Zara, Christine, Shamiran, Emma, Blake, Keiko, Plexus, Joppa, Sidewinder, Erica, Ashlei, Twilight, Kristen, Brett, Q, Enus, Simon, Bevis, Kraft, Kip, Chandler, Ron, LauraP, Ram, KyleJM, Scouse, Prospero, Melissa, Marty, Nat, Hamilton, Kend, Lordan, Jimmy, Kosmo, Seraph, Green, Ekim, Wiggo, JT, Rome, Doris, Miz, Benoc, Whump, Trinity, Patch, Kate, TJ, Bao, Joohwan, Christy, Sofia, Matias, Cogsworth, Johan, Oreh, Cheah, Angela, Brandy, Mango, Lan, Aleks, Gloria, Heidy, Mitchell, Space, Colton, Bambers, Einstein, Maggie, Malbers, Rose, Winnie, Stella, Milton, Rothman, Niall, Marin, Allison, Katie, Dawn, Katt, Dusty, Kalpana, Judy, Andrea, Ambroff, Infinity, Gail, Rico, Raymond, Yi, William, Christa, M, Teagan, Scout, Molly, Dante, Corr, Dynamike, Usi, Kaylee, Vidtuts, Lil, Danica, Sascha, Kelv, Jacob, Nya, Rodney, Brandon, Elsie, Blondin, Grant, Katrin, Nyx, Gabriel, Locklainn, Claire, Devin, Minerva, Monty, Austin, Bradford, Si, Keira, H, Caitlin, Dita, Makai, Jenn, Ann, Meredith, Clare, Joy, Praveen, Cody, Edmund, Ruthe, Sirena, Gayathri, Spider, FJ, Davidoff, Tian, Jennie, Louise, Oskar, Landon, Noelle, Jarv, Ingrid, Al, Sommer, Doc, Aria, Huin, Gray, Lili, Vir, DJ, Yang, T, Simone, Maestro, Scott, Charlene, Quixote, Amanda, Susan, Zed, Anne, Enkidu, Esbee, Joroan, Katelin, Roxie, Tay, Scarlet, Kevin, Johnny, Wolfgang, Andren, Bob, Howard, Merov, Rand, Ray, Michon, Newell, Galen, Dessie, Les 以及更多人。
-感谢以下居民,他们的帮助保证了这次第二人生的版本是有史以来最出色的: afon shepherd, Aimee Trescothick, Alexandrea Fride, Alissa Sabre, Amber DeCuir, Asuka Neely, Auron Forcella, Blue Revolution, Bocan Undercroft, Boroondas Gupte, Brandon Shinobu, Bri Gufler, Buckaroo Mu, Celierra Darling, Christos Atlantis, Coder Kas, Cummere Mayo, dakota schwade, Dirk Talamasca, Dizzy Banjo, Drew Dwi, Duckling Kwak, Ellla McMahon, Erikah Jameson, Erinyse Planer, Eyana Yohkoh, Ezian Ecksol, Faron Karu, Fenoe Lowey, Fox Hwasung, Francisca Biedermann, Gally Young, Gellan Glenelg, Geneko Nemeth, Glenn Rotaru, Hagar Qinan, Harleen Gretzky, Holger Gilruth, hotrodjohnny gears, IAm Zabelin, Inigo Catteneo, Iustinian Tomsen, Jacek Antonell, James Benedek, Jim Kupferberg, Joeseph Albanese, JPT62089 Agnon, Kardargo Adamczyk, Kirstenlee Cinquetti, Latif Khalifa, lea Parnall, Lex Neva, Lillith Anatine, Lilly Zenovka, Lim Catteneo, Lindal Kidd, Mark Rosenbaum, MasterJ Chaplin, McCabe Maxsted, Melvin Starbrook, Meni Kaiousei, Mero Collas, Minakothegothicgeisha Kamachi, Moon Metty, neofilo aabye, Neutron Chesnokov, Nomad Ingwer, norritt Xi, Opensource Obscure, Oracle Weatherwax, Ourasi Ferraris, Pabl0 Roffo, Peyton Aleixandre, Phli Foxchase, Psi Merlin, r2d2 Wunderlich, Regi Yifu, Saijanai Kuhn, Sandor Balczo, Sarkan Dreamscape, Scree Raymaker, Sedona Mills, Selena Beale, Sheet Spotter, Shibari Twine, Silver Key, Simon Kline, SLB Wirefly, Stacy Wombat, Sugarcult Dagger, Tayra Dagostino, Tetsuryu Vlodovic, ThaBiGGDoGG Richez, Timo Gufler, tx Oh, wayfinder wishbringer, Wizzytoe McCullough, Wundur Primbee, Yann Dufaux, Yuu Nakamichi
+感谢以下居民,他们的帮助保证了这次第二人生的版本是有史以来最出色的: afon shepherd, Aimee Trescothick, Alexandrea Fride, Alissa Sabre, Amber DeCuir, Asuka Neely, Auron Forcella, Blue Revolution, Bocan Undercroft, Boroondas Gupte, Brandon Shinobu, Bri Gufler, Buckaroo Mu, Celierra Darling, Christos Atlantis, Coder Kas, Cummere Mayo, dakota schwade, Dirk Talamasca, Dizzy Banjo, Drew Dwi, Duckling Kwak, Ellla McMahon, Erikah Jameson, Erinyse Planer, Eyana Yohkoh, Ezian Ecksol, Faron Karu, Fenoe Lowey, Fox Hwasung, Francisca Biedermann, Gally Young, Gellan Glenelg, Geneko Nemeth, Glenn Rotaru, Hagar Qinan, Harleen Gretzky, Holger Gilruth, hotrodjohnny gears, IAm Zabelin, Inigo Catteneo, Iustinian Tomsen, Jacek Antonell, James Benedek, Jim Kupferberg, Joeseph Albanese, JPT62089 Agnon, Kardargo Adamczyk, Kirstenlee Cinquetti, Latif Khalifa, lea Parnall, Lex Neva, Lillith Anatine, Lilly Zenovka, Lim Catteneo, Lindal Kidd, Mark Rosenbaum, MasterJ Chaplin, McCabe Maxsted, Melvin Starbrook, Meni Kaiousei, Mero Collas, Minakothegothicgeisha Kamachi, Moon Metty, neofilo aabye, Neutron Chesnokov, Nomad Ingwer, norritt Xi, Opensource Obscure, Oracle Weatherwax, Ourasi Ferraris, Pabl0 Roffo, Peyton Aleixandre, Phli Foxchase, Psi Merlin, r2d2 Wunderlich, Regi Yifu, Saijanai Kuhn, Sandor Balczo, Sarkan Dreamscape, Scree Raymaker, Sedona Mills, Selena Beale, Sheet Spotter, Shibari Twine, Silver Key, Simon Kline, SLB Wirefly, Stacy Wombat, Sugarcult Dagger, Tayra Dagostino, Tetsuryu Vlodovic, ThaBiGGDoGG Richez, Timo Gufler, tx Oh, wayfinder wishbringer, Wizzytoe McCullough, Wundur Primbee, Yann Dufaux, Yuu Nakamichi, Aminom Marvin, Andred Qinan, arminasx saiman, caroline apollo, Iskar Ariantho, Jenika Connolly, Maghnus Balogh, Nefertiti Nefarious, RodneyLee Jessop
3Dconnexion SDK Copyright © 1992-2007 3Dconnexion
--
cgit v1.1
From 18004d6835650efc03ccd6c7d3b8142b264cd95a Mon Sep 17 00:00:00 2001
From: Jacek Antonelli
Date: Sun, 1 Mar 2009 23:17:07 -0600
Subject: Second Life viewer sources 1.22.10-RC
---
linden/indra/newview/llpreviewtexture.cpp | 3 +-
linden/indra/newview/llvoiceclient.cpp | 180 ++++++++++-----------
linden/indra/newview/llvoiceclient.h | 10 +-
.../skins/default/xui/fr/floater_report_abuse.xml | 2 +-
.../indra/newview/skins/default/xui/ja/alerts.xml | 24 +--
.../skins/default/xui/ja/floater_about_land.xml | 4 +-
.../newview/skins/default/xui/ja/floater_tools.xml | 4 +-
.../newview/skins/default/xui/ja/menu_viewer.xml | 2 +-
.../skins/default/xui/ja/panel_region_general.xml | 2 +-
9 files changed, 115 insertions(+), 116 deletions(-)
(limited to 'linden/indra/newview')
diff --git a/linden/indra/newview/llpreviewtexture.cpp b/linden/indra/newview/llpreviewtexture.cpp
index 929fd15..82331b5 100644
--- a/linden/indra/newview/llpreviewtexture.cpp
+++ b/linden/indra/newview/llpreviewtexture.cpp
@@ -302,7 +302,8 @@ void LLPreviewTexture::saveAs()
if( mLoadingFullImage ) return;
LLFilePicker& file_picker = LLFilePicker::instance();
- if( !file_picker.getSaveFile( LLFilePicker::FFSAVE_TGA, LLDir::getScrubbedFileName(getItem()->getName())) )
+ const LLViewerInventoryItem* item = getItem() ;
+ if( !file_picker.getSaveFile( LLFilePicker::FFSAVE_TGA, item ? LLDir::getScrubbedFileName(item->getName()) : LLStringUtil::null) )
{
// User canceled or we failed to acquire save file.
return;
diff --git a/linden/indra/newview/llvoiceclient.cpp b/linden/indra/newview/llvoiceclient.cpp
index 145132a..76ef87d 100644
--- a/linden/indra/newview/llvoiceclient.cpp
+++ b/linden/indra/newview/llvoiceclient.cpp
@@ -1362,10 +1362,13 @@ void LLVoiceClient::login(
mVoiceSIPURIHostName = voice_sip_uri_hostname;
mVoiceAccountServerURI = voice_account_server_uri;
- if((getState() >= stateLoggingIn) && (getState() < stateLoggedOut))
+ if(!mAccountHandle.empty())
{
- // Already logged in. This is an internal error.
- LL_ERRS("Voice") << "Can't login again. Called from wrong state." << LL_ENDL;
+ // Already logged in.
+ LL_WARNS("Voice") << "Called while already logged in." << LL_ENDL;
+
+ // Don't process another login.
+ return;
}
else if ( account_name != mAccountName )
{
@@ -1431,12 +1434,16 @@ std::string LLVoiceClient::state2string(LLVoiceClient::state inState)
switch(inState)
{
+ CASE(stateDisableCleanup);
CASE(stateDisabled);
CASE(stateStart);
CASE(stateDaemonLaunched);
CASE(stateConnecting);
+ CASE(stateConnected);
CASE(stateIdle);
- CASE(stateNeedsProvision);
+ CASE(stateMicTuningStart);
+ CASE(stateMicTuningRunning);
+ CASE(stateMicTuningStop);
CASE(stateConnectorStart);
CASE(stateConnectorStarting);
CASE(stateConnectorStarted);
@@ -1447,9 +1454,6 @@ std::string LLVoiceClient::state2string(LLVoiceClient::state inState)
CASE(stateLoggedIn);
CASE(stateCreatingSessionGroup);
CASE(stateNoChannel);
- CASE(stateMicTuningStart);
- CASE(stateMicTuningRunning);
- CASE(stateMicTuningStop);
CASE(stateJoiningSession);
CASE(stateSessionJoined);
CASE(stateRunning);
@@ -1466,7 +1470,6 @@ std::string LLVoiceClient::state2string(LLVoiceClient::state inState)
CASE(stateJoinSessionFailed);
CASE(stateJoinSessionFailedWaiting);
CASE(stateJail);
- CASE(stateMicTuningNoLogin);
}
#undef CASE
@@ -1522,9 +1525,13 @@ void LLVoiceClient::stateMachine()
{
updatePosition();
}
+ else if(mTuningMode)
+ {
+ // Tuning mode is special -- it needs to launch SLVoice even if voice is disabled.
+ }
else
{
- if(getState() != stateDisabled)
+ if((getState() != stateDisabled) && (getState() != stateDisableCleanup))
{
// User turned off voice support. Send the cleanup messages, close the socket, and reset.
if(!mConnected)
@@ -1534,16 +1541,10 @@ void LLVoiceClient::stateMachine()
killGateway();
}
-// leaveAudioSession();
logout();
- // As of SDK version 4885, this should no longer be necessary. It will linger after the socket close if it needs to.
- // ms_sleep(2000);
connectorShutdown();
- closeSocket();
- deleteAllSessions();
- deleteAllBuddies();
- setState(stateDisabled);
+ setState(stateDisableCleanup);
}
}
@@ -1579,9 +1580,24 @@ void LLVoiceClient::stateMachine()
switch(getState())
{
+ //MARK: stateDisableCleanup
+ case stateDisableCleanup:
+ // Clean up and reset everything.
+ closeSocket();
+ deleteAllSessions();
+ deleteAllBuddies();
+
+ mConnectorHandle.clear();
+ mAccountHandle.clear();
+ mAccountPassword.clear();
+ mVoiceAccountServerURI.clear();
+
+ setState(stateDisabled);
+ break;
+
//MARK: stateDisabled
case stateDisabled:
- if(mVoiceEnabled && (!mAccountName.empty() || mTuningMode))
+ if(mTuningMode || (mVoiceEnabled && !mAccountName.empty()))
{
setState(stateStart);
}
@@ -1780,29 +1796,34 @@ void LLVoiceClient::stateMachine()
mPump->addChain(readChain, NEVER_CHAIN_EXPIRY_SECS);
- setState(stateIdle);
+ setState(stateConnected);
}
break;
- //MARK: stateIdle
- case stateIdle:
+ //MARK: stateConnected
+ case stateConnected:
// Initial devices query
getCaptureDevicesSendMessage();
getRenderDevicesSendMessage();
mLoginRetryCount = 0;
-
- setState(stateNeedsProvision);
-
+
+ setState(stateIdle);
break;
-
- //MARK: stateNeedsProvision
- case stateNeedsProvision:
- if(!mVoiceEnabled)
+
+ //MARK: stateIdle
+ case stateIdle:
+ // This is the idle state where we're connected to the daemon but haven't set up a connector yet.
+ if(mTuningMode)
{
- // We were never logged in. This will shut down the connector.
- setState(stateLoggedOut);
+ mTuningExitState = stateIdle;
+ setState(stateMicTuningStart);
+ }
+ else if(!mVoiceEnabled)
+ {
+ // We never started up the connector. This will shut down the daemon.
+ setState(stateConnectorStopped);
}
else if(!mAccountName.empty())
{
@@ -1820,50 +1841,8 @@ void LLVoiceClient::stateMachine()
}
}
}
- else if(mTuningMode)
- {
- mTuningExitState = stateNeedsProvision;
- setState(stateMicTuningStart);
- }
- break;
-
- //MARK: stateConnectorStart
- case stateConnectorStart:
- if(!mVoiceEnabled)
- {
- // We were never logged in. This will shut down the connector.
- setState(stateLoggedOut);
- }
- else if(!mVoiceAccountServerURI.empty())
- {
- connectorCreate();
- }
- else if(mTuningMode)
- {
- mTuningExitState = stateConnectorStart;
- setState(stateMicTuningStart);
- }
- break;
-
- //MARK: stateConnectorStarting
- case stateConnectorStarting: // waiting for connector handle
- // connectorCreateResponse() will transition from here to stateConnectorStarted.
- break;
-
- //MARK: stateConnectorStarted
- case stateConnectorStarted: // connector handle received
- if(!mVoiceEnabled)
- {
- // We were never logged in. This will shut down the connector.
- setState(stateLoggedOut);
- }
- else
- {
- // The connector is started. Send a login message.
- setState(stateNeedsLogin);
- }
break;
-
+
//MARK: stateMicTuningStart
case stateMicTuningStart:
if(mUpdateTimer.hasExpired())
@@ -1898,7 +1877,7 @@ void LLVoiceClient::stateMachine()
//MARK: stateMicTuningRunning
case stateMicTuningRunning:
- if(!mTuningMode || !mVoiceEnabled || mSessionTerminateRequested || mCaptureDeviceDirty || mRenderDeviceDirty)
+ if(!mTuningMode || mCaptureDeviceDirty || mRenderDeviceDirty)
{
// All of these conditions make us leave tuning mode.
setState(stateMicTuningStop);
@@ -1952,7 +1931,39 @@ void LLVoiceClient::stateMachine()
}
break;
-
+
+ //MARK: stateConnectorStart
+ case stateConnectorStart:
+ if(!mVoiceEnabled)
+ {
+ // We were never logged in. This will shut down the connector.
+ setState(stateLoggedOut);
+ }
+ else if(!mVoiceAccountServerURI.empty())
+ {
+ connectorCreate();
+ }
+ break;
+
+ //MARK: stateConnectorStarting
+ case stateConnectorStarting: // waiting for connector handle
+ // connectorCreateResponse() will transition from here to stateConnectorStarted.
+ break;
+
+ //MARK: stateConnectorStarted
+ case stateConnectorStarted: // connector handle received
+ if(!mVoiceEnabled)
+ {
+ // We were never logged in. This will shut down the connector.
+ setState(stateLoggedOut);
+ }
+ else
+ {
+ // The connector is started. Send a login message.
+ setState(stateNeedsLogin);
+ }
+ break;
+
//MARK: stateLoginRetry
case stateLoginRetry:
if(mLoginRetryCount == 0)
@@ -2311,11 +2322,7 @@ void LLVoiceClient::stateMachine()
//MARK: stateConnectorStopped
case stateConnectorStopped: // connector stop received
- // Clean up and reset everything.
- closeSocket();
- deleteAllSessions();
- deleteAllBuddies();
- setState(stateDisabled);
+ setState(stateDisableCleanup);
break;
//MARK: stateConnectorFailed
@@ -2366,11 +2373,6 @@ void LLVoiceClient::stateMachine()
// We have given up. Do nothing.
break;
- //MARK: stateMicTuningNoLogin
- case stateMicTuningNoLogin:
- // *TODO: Implement me.
- LL_WARNS("Voice") << "stateMicTuningNoLogin not handled" << LL_ENDL;
- break;
}
if(mAudioSession && mAudioSession->mParticipantsChanged)
@@ -2943,12 +2945,8 @@ void LLVoiceClient::daemonDied()
// The daemon died, so the connection is gone. Reset everything and start over.
LL_WARNS("Voice") << "Connection to vivox daemon lost. Resetting state."<< LL_ENDL;
- closeSocket();
- deleteAllSessions();
- deleteAllBuddies();
-
// Try to relaunch the daemon
- setState(stateDisabled);
+ setState(stateDisableCleanup);
}
void LLVoiceClient::giveUp()
@@ -5631,8 +5629,8 @@ void LLVoiceClient::setVoiceEnabled(bool enabled)
}
else
{
- // for now, leave active channel, to auto join when turning voice back on
- //LLVoiceChannel::getCurrentVoiceChannel->deactivate();
+ // Turning voice off looses your current channel -- this makes sure the UI isn't out of sync when you re-enable it.
+ LLVoiceChannel::getCurrentVoiceChannel()->deactivate();
}
}
}
diff --git a/linden/indra/newview/llvoiceclient.h b/linden/indra/newview/llvoiceclient.h
index 9fc6a7d..13dd974 100644
--- a/linden/indra/newview/llvoiceclient.h
+++ b/linden/indra/newview/llvoiceclient.h
@@ -502,16 +502,19 @@ static void updatePosition(void);
// Note: if you change this list, please make corresponding changes to LLVoiceClient::state2string().
enum state
{
+ stateDisableCleanup,
stateDisabled, // Voice is turned off.
stateStart, // Class is initialized, socket is created
stateDaemonLaunched, // Daemon has been launched
stateConnecting, // connect() call has been issued
+ stateConnected, // connection to the daemon has been made, send some initial setup commands.
stateIdle, // socket is connected, ready for messaging
- stateNeedsProvision, // Need to do a ProvisionVoiceAccountRequest
+ stateMicTuningStart,
+ stateMicTuningRunning,
+ stateMicTuningStop,
stateConnectorStart, // connector needs to be started
stateConnectorStarting, // waiting for connector handle
stateConnectorStarted, // connector handle received
- stateMicTuningNoLogin, // mic tuning before login
stateLoginRetry, // need to retry login (failed due to changing password)
stateLoginRetryWait, // waiting for retry timer
stateNeedsLogin, // send login request
@@ -519,9 +522,6 @@ static void updatePosition(void);
stateLoggedIn, // account handle received
stateCreatingSessionGroup, // Creating the main session group
stateNoChannel, //
- stateMicTuningStart,
- stateMicTuningRunning,
- stateMicTuningStop,
stateJoiningSession, // waiting for session handle
stateSessionJoined, // session handle received
stateRunning, // in session, steady state
diff --git a/linden/indra/newview/skins/default/xui/fr/floater_report_abuse.xml b/linden/indra/newview/skins/default/xui/fr/floater_report_abuse.xml
index 1fbc727..c9b065c 100644
--- a/linden/indra/newview/skins/default/xui/fr/floater_report_abuse.xml
+++ b/linden/indra/newview/skins/default/xui/fr/floater_report_abuse.xml
@@ -172,7 +172,7 @@
Indiquez la date, le lieu, la nature de l'infraction, ainsi que
tout chat ou IM relatif à l'infraction, en étant aussi précis
-que possible. Pensez à indiquer un objet.
+que possible. Pensez à indiquer un objet si possible.
Remarque : les rapports incomplets ne feront pas l'objet d'une
diff --git a/linden/indra/newview/skins/default/xui/ja/alerts.xml b/linden/indra/newview/skins/default/xui/ja/alerts.xml
index f4512c2..30ba935 100644
--- a/linden/indra/newview/skins/default/xui/ja/alerts.xml
+++ b/linden/indra/newview/skins/default/xui/ja/alerts.xml
@@ -2797,10 +2797,10 @@ F1キーを押してください。
- お使いのシステムのウェブ・ブラウザーを開いて、この内容を表示しますか?
+ お使いのシステムのウェブ・ブラウザを開いて、この内容を表示しますか?
- お使いのシステムのブラウザーを開いてウェブ・ページを表示しようとしたとき
+ お使いのシステムのブラウザを開いてウェブ・ページを表示しようとしたとき
- [SECOND_LIFE] QA Wikiをご覧ください。
+ [SECOND_LIFE] 品質保証関連Wikiをご覧ください。
- QA Wikiを閲覧するためにウェブ・ブラウザを起動するにあたって
+ 品質保証関連Wikiを閲覧するためにウェブ・ブラウザを起動するにあたって
- 最新のヒントおよびトリックについては、[SECOND_LIFE]の知識ベースを検索してください。
+ 最新のヒントおよびトリックについては、[SECOND_LIFE]のナレッジベースを検索してください。
- 知識ベースを表示するため、ウェブ・ブラウザーを起動するとき
+ ナレッジベースを表示するため、ウェブ・ブラウザを起動するとき