source: psi/trunk/src/common.cpp@ 77

Last change on this file since 77 was 76, checked in by dmik, 19 years ago

Psi: Changed the application name to Psi/2; changed version to 0.10.1.

File size: 46.6 KB
Line 
1/*
2 * common.cpp - contains all the common variables and functions for Psi
3 * Copyright (C) 2001-2003 Justin Karneges
4 *
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License
7 * as published by the Free Software Foundation; either version 2
8 * of the License, or (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this library; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 *
19 */
20
21#include"common.h"
22
23#include"profiles.h"
24#include"rtparse.h"
25
26#include"psievent.h"
27
28QString PROG_NAME = "Psi/2";
29QString PROG_VERSION = "0.10.1";
30//QString PROG_VERSION = "0.10.1-dev (" __DATE__ ")";
31
32#ifdef HAVE_CONFIG
33#include "config.h"
34#endif
35
36#ifndef PSI_DATADIR
37#define PSI_DATADIR "/usr/local/share/psi"
38#endif
39
40#include<qregexp.h>
41#include<qdir.h>
42#include<qfile.h>
43#include<qapplication.h>
44#include<qsound.h>
45#include<qobjectlist.h>
46#include<qlibrary.h>
47#include<qstylesheet.h>
48
49#include<qthread.h>
50#include<qmutex.h>
51#include<qsemaphore.h>
52#include<qvaluelist.h>
53
54
55#include<stdio.h>
56
57#ifdef Q_WS_X11
58#include<sys/types.h>
59#include<sys/stat.h>
60#include<sys/wait.h>
61#endif
62
63#ifdef Q_WS_WIN
64#include<windows.h>
65#endif
66
67#ifdef Q_WS_PM
68#include<sys/types.h>
69#include<sys/stat.h>
70#endif
71
72#ifdef Q_WS_MAC
73#include<sys/types.h>
74#include<sys/stat.h>
75#include <Carbon/Carbon.h> // for HIToolbox/InternetConfig
76#include <CoreServices/CoreServices.h>
77#endif
78
79QString activeProfile;
80
81//QStringList dtcp_hostList;
82//int dtcp_port;
83//QString dtcp_proxy;
84//bool link_test = false;
85bool use_gpg = true;
86bool no_gpg_agent = false;
87uint psi_dialog_flags = (Qt::WStyle_SysMenu | Qt::WStyle_MinMax);
88//uint psi_dialog_flags = (Qt::WStyle_Customize | Qt::WStyle_DialogBorder | Qt::WStyle_Title | Qt::WStyle_SysMenu | Qt::WStyle_MinMax);
89
90char *pixdat_ft_back, *pixdat_ft_file, *pixdat_ft_folder;
91int pixlen_ft_back, pixlen_ft_file, pixlen_ft_folder;
92
93
94XMPP::Client *debug_jabber = 0;
95Options option;
96PsiGlobal g;
97PsiIconset *is;
98bool useSound;
99
100
101QString qstrlower(QString str)
102{
103 for(unsigned int n = 0; n < str.length(); ++n) {
104 str.at(n) = str.at(n).lower();
105 }
106
107 return str;
108}
109
110int qstrcasecmp(const QString &str1, const QString &str2)
111{
112 if(str1.length() != str2.length())
113 return 1;
114
115 for(unsigned int n = 0; n < str1.length(); ++n) {
116 if(str1.at(n).lower() != str2.at(n).lower())
117 return 1;
118 }
119
120 return 0;
121}
122
123int qstringlistmatch(QStringList &list, const QString &str)
124{
125 int n = 0;
126
127 for(QStringList::Iterator i = list.begin(); i != list.end(); ++i, ++n) {
128 if(*i == str)
129 return n;
130 }
131
132 return -1;
133}
134
135QString qstringlistlookup(QStringList &list, int x)
136{
137 int n = 0;
138 QStringList::Iterator i = list.begin();
139 for(;i != list.end() && n < x; ++i, ++n);
140 if(n != x)
141 return "";
142
143 return *i;
144}
145
146QString CAP(const QString &str)
147{
148 return QString("%1: %2").arg(PROG_NAME).arg(str);
149}
150
151QString eatptag(QString txt)
152{
153 if(txt.left(3) == "<p>")
154 txt = txt.mid(3, txt.length() - 7); // strlen("<p>") + strlen("</p>") = 7
155
156 return txt;
157}
158
159QString qstrquote(const QString &toquote, int width, bool quoteEmpty)
160{
161 int ql = 0, col = 0, atstart = 1, ls=0;
162
163 QString quoted = "> "+toquote; // quote first line
164 QString rxs = quoteEmpty ? "\n" : "\n(?!\\s*\n)";
165 QRegExp rx(rxs); // quote following lines
166 quoted.replace(rx, "\n> ");
167 rx.setPattern("> +>"); // compress > > > > quotes to >>>>
168 quoted.replace(rx, ">>");
169 quoted.replace(rx, ">>");
170 quoted.replace(QRegExp(" +\n"), "\n"); // remove trailing spaces
171
172 if (!quoteEmpty)
173 {
174 quoted.replace(QRegExp("^>+\n"), "\n\n"); // unquote empty lines
175 quoted.replace(QRegExp("\n>+\n"), "\n\n");
176 }
177
178
179 for (int i=0;i<(int) quoted.length();i++)
180 {
181 col++;
182 if (atstart && quoted[i] == '>') ql++; else atstart=0;
183
184 switch(quoted[i].latin1())
185 {
186 case '\n': ql = col = 0; atstart = 1; break;
187 case ' ':
188 case '\t': ls = i; break;
189
190 }
191 if (quoted[i]=='\n') { ql=0; atstart = 1; }
192
193 if (col > width)
194 {
195 if ((ls+width) < i)
196 {
197 ls = i; i = quoted.length();
198 while ((ls<i) && !quoted[ls].isSpace()) ls++;
199 i = ls;
200 }
201 if ((i<(int)quoted.length()) && (quoted[ls] != '\n'))
202 {
203 quoted.insert(ls, '\n');
204 ++ls;
205 quoted.insert(ls, QString().fill('>', ql));
206 i += ql+1;
207 col = 0;
208 }
209 }
210 }
211 quoted += "\n\n";// add two empty lines to quoted text - the cursor
212 // will be positioned at the end of those.
213 return quoted;
214}
215
216QString plain2rich(const QString &plain)
217{
218 QString rich;
219 int col = 0;
220
221 for(int i = 0; i < (int)plain.length(); ++i) {
222 if(plain[i] == '\n') {
223 rich += "<br>";
224 col = 0;
225 }
226 else if(plain[i] == '\t') {
227 rich += QChar::nbsp;
228 while(col % 4) {
229 rich += QChar::nbsp;
230 ++col;
231 }
232 }
233 else if(plain[i].isSpace()) {
234 if(i > 0 && plain[i-1] == ' ')
235 rich += QChar::nbsp;
236 else
237 rich += ' ';
238 }
239 else if(plain[i] == '<')
240 rich += "&lt;";
241 else if(plain[i] == '>')
242 rich += "&gt;";
243 else if(plain[i] == '\"')
244 rich += "&quot;";
245 else if(plain[i] == '\'')
246 rich += "&apos;";
247 else if(plain[i] == '&')
248 rich += "&amp;";
249 else
250 rich += plain[i];
251 ++col;
252 }
253
254 return rich;
255}
256
257QString rich2plain(const QString &in)
258{
259 QString out;
260
261 for(int i = 0; i < (int)in.length(); ++i) {
262 // tag?
263 if(in[i] == '<') {
264 // find end of tag
265 ++i;
266 int n = in.find('>', i);
267 if(n == -1)
268 break;
269 QString str = in.mid(i, (n-i));
270 i = n;
271
272 QString tagName;
273 n = str.find(' ');
274 if(n != -1)
275 tagName = str.mid(0, n);
276 else
277 tagName = str;
278
279 if(tagName == "br")
280 out += '\n';
281 }
282 // entity?
283 else if(in[i] == '&') {
284 // find a semicolon
285 ++i;
286 int n = in.find(';', i);
287 if(n == -1)
288 break;
289 QString type = in.mid(i, (n-i));
290 i = n; // should be n+1, but we'll let the loop increment do it
291
292 if(type == "amp")
293 out += '&';
294 else if(type == "lt")
295 out += '<';
296 else if(type == "gt")
297 out += '>';
298 else if(type == "quot")
299 out += '\"';
300 else if(type == "apos")
301 out += '\'';
302 }
303 else if(in[i].isSpace()) {
304 if(in[i] == QChar::nbsp)
305 out += ' ';
306 else if(in[i] != '\n') {
307 if(i == 0)
308 out += ' ';
309 else {
310 QChar last = out.at(out.length()-1);
311 bool ok = TRUE;
312 if(last.isSpace() && last != '\n')
313 ok = FALSE;
314 if(ok)
315 out += ' ';
316 }
317 }
318 }
319 else {
320 out += in[i];
321 }
322 }
323
324 return out;
325}
326
327
328// clips plain text
329QString clipStatus(const QString &str, int width, int height)
330{
331 QString out = "";
332 int at = 0;
333 int len = str.length();
334 if(len == 0)
335 return out;
336
337 // only take the first "height" lines
338 for(int n2 = 0; n2 < height; ++n2) {
339 // only take the first "width" chars
340 QString line;
341 bool hasNewline = false;
342 for(int n = 0; at < len; ++n, ++at) {
343 if(str.at(at) == '\n') {
344 hasNewline = true;
345 break;
346 }
347 line += str.at(at);
348 }
349 ++at;
350 if((int)line.length() > width) {
351 line.truncate(width-3);
352 line += "...";
353 }
354 out += line;
355 if(hasNewline)
356 out += '\n';
357
358 if(at >= len)
359 break;
360 }
361
362 return out;
363}
364
365QString expandEntities(const QString &in)
366{
367 return QStyleSheet::escape(in);
368}
369
370QString resolveEntities(const QString &in)
371{
372 QString out;
373
374 for(int i = 0; i < (int)in.length(); ++i) {
375 if(in[i] == '&') {
376 // find a semicolon
377 ++i;
378 int n = in.find(';', i);
379 if(n == -1)
380 break;
381 QString type = in.mid(i, (n-i));
382 i = n; // should be n+1, but we'll let the loop increment do it
383
384 if(type == "amp")
385 out += '&';
386 else if(type == "lt")
387 out += '<';
388 else if(type == "gt")
389 out += '>';
390 else if(type == "quot")
391 out += '\"';
392 else if(type == "apos")
393 out += '\'';
394 }
395 else {
396 out += in[i];
397 }
398 }
399
400 return out;
401}
402
403
404static bool linkify_pmatch(const QString &str1, int at, const QString &str2)
405{
406 if(str2.length() > (str1.length()-at))
407 return FALSE;
408
409 for(int n = 0; n < (int)str2.length(); ++n) {
410 if(str1.at(n+at).lower() != str2.at(n).lower())
411 return FALSE;
412 }
413
414 return TRUE;
415}
416
417static bool linkify_isOneOf(const QChar &c, const QString &charlist)
418{
419 for(int i = 0; i < (int)charlist.length(); ++i) {
420 if(c == charlist.at(i))
421 return TRUE;
422 }
423
424 return FALSE;
425}
426
427// encodes a few dangerous html characters
428static QString linkify_htmlsafe(const QString &in)
429{
430 QString out;
431
432 for(unsigned int n = 0; n < in.length(); ++n) {
433 if(linkify_isOneOf(in.at(n), "\"\'`<>")) {
434 // hex encode
435 QString hex;
436 hex.sprintf("%%%02X", in.at(n).latin1());
437 out.append(hex);
438 }
439 else {
440 out.append(in.at(n));
441 }
442 }
443
444 return out;
445}
446
447static bool linkify_okUrl(const QString &url)
448{
449 if(url.at(url.length()-1) == '.')
450 return FALSE;
451
452 return TRUE;
453}
454
455static bool linkify_okEmail(const QString &addy)
456{
457 // this makes sure that there is an '@' and a '.' after it, and that there is
458 // at least one char for each of the three sections
459 int n = addy.find('@');
460 if(n == -1 || n == 0)
461 return FALSE;
462 int d = addy.find('.', n+1);
463 if(d == -1 || d == 0)
464 return FALSE;
465 if((addy.length()-1) - d <= 0)
466 return FALSE;
467 if(addy.find("..") != -1)
468 return false;
469
470 return TRUE;
471}
472
473QString linkify(const QString &in)
474{
475 QString out = in;
476 int x1, x2;
477 bool isUrl, isEmail;
478 QString linked, link, href;
479
480 for(int n = 0; n < (int)out.length(); ++n) {
481 isUrl = FALSE;
482 isEmail = FALSE;
483 x1 = n;
484
485 if(linkify_pmatch(out, n, "http://")) {
486 n += 7;
487 isUrl = TRUE;
488 href = "";
489 }
490 else if(linkify_pmatch(out, n, "https://")) {
491 n += 8;
492 isUrl = TRUE;
493 href = "";
494 }
495 else if(linkify_pmatch(out, n, "ftp://")) {
496 n += 6;
497 isUrl = TRUE;
498 href = "";
499 }
500 else if(linkify_pmatch(out, n, "news://")) {
501 n += 7;
502 isUrl = TRUE;
503 href = "";
504 }
505 else if (linkify_pmatch(out, n, "ed2k://")) {
506 n += 7;
507 isUrl = TRUE;
508 href = "";
509 }
510 else if(linkify_pmatch(out, n, "www.")) {
511 isUrl = TRUE;
512 href = "http://";
513 }
514 else if(linkify_pmatch(out, n, "ftp.")) {
515 isUrl = TRUE;
516 href = "ftp://";
517 }
518 else if(linkify_pmatch(out, n, "@")) {
519 isEmail = TRUE;
520 href = "mailto:";
521 }
522
523 if(isUrl) {
524 // make sure the previous char is not alphanumeric
525 if(x1 > 0 && out.at(x1-1).isLetterOrNumber())
526 continue;
527
528 // find whitespace (or end)
529 for(x2 = n; x2 < (int)out.length(); ++x2) {
530 if(out.at(x2).isSpace() || out.at(x2) == '<')
531 break;
532 }
533 int len = x2-x1;
534 QString pre = resolveEntities(out.mid(x1, x2-x1));
535
536 // go backward hacking off unwanted punctuation
537 int cutoff;
538 for(cutoff = pre.length()-1; cutoff >= 0; --cutoff) {
539 if(!linkify_isOneOf(pre.at(cutoff), "!?,.()[]{}<>\""))
540 break;
541 }
542 ++cutoff;
543 //++x2;
544
545 link = pre.mid(0, cutoff);
546 if(!linkify_okUrl(link)) {
547 n = x1 + link.length();
548 continue;
549 }
550 href += link;
551 href = linkify_htmlsafe(href);
552 //printf("link: [%s], href=[%s]\n", link.latin1(), href.latin1());
553 linked = QString("<a href=\"%1\">").arg(href) + expandEntities(link) + "</a>" + expandEntities(pre.mid(cutoff));
554 out.replace(x1, len, linked);
555 n = x1 + linked.length() - 1;
556 }
557 else if(isEmail) {
558 // go backward till we find the beginning
559 if(x1 == 0)
560 continue;
561 --x1;
562 for(; x1 >= 0; --x1) {
563 if(!linkify_isOneOf(out.at(x1), "_.-") && !out.at(x1).isLetterOrNumber())
564 break;
565 }
566 ++x1;
567
568 // go forward till we find the end
569 x2 = n + 1;
570 for(; x2 < (int)out.length(); ++x2) {
571 if(!linkify_isOneOf(out.at(x2), "_.-") && !out.at(x2).isLetterOrNumber())
572 break;
573 }
574
575 int len = x2-x1;
576 link = out.mid(x1, len);
577 //link = resolveEntities(link);
578
579 if(!linkify_okEmail(link)) {
580 n = x1 + link.length();
581 continue;
582 }
583
584 href += link;
585 //printf("link: [%s], href=[%s]\n", link.latin1(), href.latin1());
586 linked = QString("<a href=\"%1\">").arg(href) + link + "</a>";
587 out.replace(x1, len, linked);
588 n = x1 + linked.length() - 1;
589 }
590 }
591
592 return out;
593}
594
595// sickening
596QString emoticonify(const QString &in)
597{
598 RTParse p(in);
599 while ( !p.atEnd() ) {
600 // returns us the first chunk as a plaintext string
601 QString str = p.next();
602
603 int i = 0;
604 while ( i >= 0 ) {
605 // find closest emoticon
606 int ePos = -1;
607 Icon *closest = 0;
608
609 int foundPos = -1, foundLen = -1;
610
611 QPtrListIterator<Iconset> iconsets(is->emoticons);
612 Iconset *iconset;
613 while ( (iconset = iconsets.current()) != 0 ) {
614 QPtrListIterator<Icon> it = iconset->iterator();
615 for ( ; it.current(); ++it) {
616 Icon *icon = it.current();
617 if ( icon->regExp().isEmpty() )
618 continue;
619
620 // some hackery
621 int iii = i;
622 bool searchAgain;
623
624 do {
625 searchAgain = false;
626
627 // find the closest match
628 const QRegExp &rx = icon->regExp();
629 int n = rx.search(str, iii);
630 if ( n == -1 )
631 continue;
632
633 if(ePos == -1 || n < ePos || (rx.matchedLength() > foundLen && n < ePos + foundLen)) {
634 // there must be whitespace at least on one side of the emoticon
635 if ( ( n == 0 ) ||
636 ( n+rx.matchedLength() == (int)str.length() ) ||
637 ( n > 0 && str[n-1].isSpace() ) ||
638 ( n+rx.matchedLength() < (int)str.length() && str[n+rx.matchedLength()].isSpace() ) )
639 {
640 ePos = n;
641 closest = icon;
642
643 foundPos = n;
644 foundLen = rx.matchedLength();
645 break;
646 }
647
648 searchAgain = true;
649 }
650
651 iii = n + rx.matchedLength();
652 } while ( searchAgain );
653 }
654
655 ++iconsets;
656 }
657
658 QString s;
659 if(ePos == -1)
660 s = str.mid(i);
661 else
662 s = str.mid(i, ePos-i);
663 p.putPlain(s);
664
665 if ( !closest )
666 break;
667
668 p.putRich( QString("<icon name=\"%1\" text=\"%2\">").arg(closest->name()).arg(str.mid(foundPos, foundLen)) );
669 i = foundPos + foundLen;
670 }
671 }
672
673 QString out = p.output();
674
675 //enable *bold* stuff
676 // //old code
677 //out=out.replace(QRegExp("(^[^<>\\s]*|\\s[^<>\\s]*)\\*(\\S+)\\*([^<>\\s]*\\s|[^<>\\s]*$)"),"\\1<b>*\\2*</b>\\3");
678 //out=out.replace(QRegExp("(^[^<>\\s\\/]*|\\s[^<>\\s\\/]*)\\/([^\\/\\s]+)\\/([^<>\\s\\/]*\\s|[^<>\\s\\/]*$)"),"\\1<i>/\\2/</i>\\3");
679 //out=out.replace(QRegExp("(^[^<>\\s]*|\\s[^<>\\s]*)_(\\S+)_([^<>\\s]*\\s|[^<>\\s]*$)"),"\\1<u>_\\2_</u>\\3");
680
681 out=out.replace(QRegExp("(^_|\\s_)(\\S+)(_\\s|_$)"),"\\1<u>\\2</u>\\3");
682 out=out.replace(QRegExp("(^\\*|\\s\\*)(\\S+)(\\*\\s|\\*$)"),"\\1<b>\\2</b>\\3");
683 out=out.replace(QRegExp("(^\\/|\\s\\/)(\\S+)(\\/\\s|\\/$)"),"\\1<i>\\2</i>\\3");
684
685 return out;
686}
687
688QString encodePassword(const QString &pass, const QString &key)
689{
690 QString result;
691 unsigned int n1, n2;
692
693 if(key.length() == 0)
694 return pass;
695
696 for(n1 = 0, n2 = 0; n1 < pass.length(); ++n1) {
697 ushort x = pass.at(n1).unicode() ^ key.at(n2++).unicode();
698 QString hex;
699 hex.sprintf("%04x", x);
700 result += hex;
701 if(n2 >= key.length())
702 n2 = 0;
703 }
704 return result;
705}
706
707QString decodePassword(const QString &pass, const QString &key)
708{
709 QString result;
710 unsigned int n1, n2;
711
712 if(key.length() == 0)
713 return pass;
714
715 for(n1 = 0, n2 = 0; n1 < pass.length(); n1 += 4) {
716 ushort x = 0;
717 if(n1 + 4 > pass.length())
718 break;
719 x += hexChar2int(pass.at(n1))*4096;
720 x += hexChar2int(pass.at(n1+1))*256;
721 x += hexChar2int(pass.at(n1+2))*16;
722 x += hexChar2int(pass.at(n1+3));
723 QChar c(x ^ key.at(n2++).unicode());
724 result += c;
725 if(n2 >= key.length())
726 n2 = 0;
727 }
728 return result;
729}
730
731QString status2txt(int status)
732{
733 switch(status) {
734 case STATUS_OFFLINE: return QObject::tr("Offline");
735 case STATUS_AWAY: return QObject::tr("Away");
736 case STATUS_XA: return QObject::tr("Not Available");
737 case STATUS_DND: return QObject::tr("Do not Disturb");
738 case STATUS_CHAT: return QObject::tr("Free for Chat");
739 case STATUS_INVISIBLE: return QObject::tr("Invisible");
740
741 case STATUS_ONLINE:
742 default: return QObject::tr("Online");
743 }
744}
745
746Icon category2icon(const QString &category, const QString &type)
747{
748 // TODO: update this to http://www.jabber.org/registrar/disco-categories.html#gateway
749
750 // still have to add more options...
751 if ( category == "service" || category == "gateway" ) {
752 QString trans;
753
754 if (type == "aim")
755 trans = "aim";
756 else if (type == "icq")
757 trans = "icq";
758 else if (type == "msn")
759 trans = "msn";
760 else if (type == "yahoo")
761 trans = "yahoo";
762 else if (type == "gadu-gadu" || type == "x-gadugadu")
763 trans = "gadugadu";
764 else if (type == "sms")
765 trans = "sms";
766 else
767 trans = "transport";
768
769 return is->transportStatus(trans, STATUS_ONLINE);
770
771 // irc
772 // jud
773 // pager
774 // jabber
775 // serverlist
776 // smtp
777 }
778 else if ( category == "conference" ) {
779 if (type == "public" || type == "private" || type == "text" || type == "irc")
780 return IconsetFactory::icon("psi/groupChat");
781 else if (type == "url")
782 return IconsetFactory::icon("psi/www");
783 // irc
784 // list
785 // topic
786 }
787 else if ( category == "validate" ) {
788 if (type == "xml")
789 return IconsetFactory::icon("psi/xml");
790 // grammar
791 // spell
792 }
793 else if ( category == "user" || category == "client" ) {
794 // client
795 // forward
796 // inbox
797 // portable
798 // voice
799 return is->status(STATUS_ONLINE);
800 }
801 // application
802 // bot
803 // calendar
804 // editor
805 // fileserver
806 // game
807 // whiteboard
808 // headline
809 // logger
810 // notice
811 // rss
812 // stock
813 // keyword
814 // dictionary
815 // dns
816 // software
817 // thesaurus
818 // web
819 // whois
820 // render
821 // en2ru
822 // ??2??
823 // tts
824 return Icon();
825}
826
827int hexChar2int(char c)
828{
829 if(c >= 'A' && c <= 'F')
830 return c - 'A' + 10;
831 else if(c >= 'a' && c <= 'f')
832 return c - 'a' + 10;
833 else if(c >= '0' && c <= '9')
834 return c - '0';
835
836 return 0;
837}
838
839char int2hexChar(int x)
840{
841 if(x < 10)
842 return (char)x + '0';
843 else
844 return (char)x - 10 + 'a';
845}
846
847QString jidEncode(const QString &jid)
848{
849 QString jid2;
850
851 for(unsigned int n = 0; n < jid.length(); ++n) {
852 if(jid.at(n) == '@') {
853 jid2.append("_at_");
854 }
855 else if(jid.at(n) == '.') {
856 jid2.append('.');
857 }
858 else if(!jid.at(n).isLetterOrNumber()) {
859 // hex encode
860 QString hex;
861 hex.sprintf("%%%02X", jid.at(n).latin1());
862 jid2.append(hex);
863 }
864 else {
865 jid2.append(jid.at(n));
866 }
867 }
868
869 return jid2;
870}
871
872QString jidDecode(const QString &jid)
873{
874 QString jid2;
875 int n;
876
877 for(n = 0; n < (int)jid.length(); ++n) {
878 if(jid.at(n) == '%' && (jid.length() - n - 1) >= 2) {
879 QString str = jid.mid(n+1,2);
880 bool ok;
881 char c = str.toInt(&ok, 16);
882 if(!ok)
883 continue;
884
885 QChar a(c);
886 jid2.append(a);
887 n += 2;
888 }
889 else {
890 jid2.append(jid.at(n));
891 }
892 }
893
894 // search for the _at_ backwards, just in case
895 for(n = (int)jid2.length(); n >= 3; --n) {
896 if(jid2.mid(n, 4) == "_at_") {
897 jid2.replace(n, 4, "@");
898 break;
899 }
900 }
901
902 return jid2;
903}
904
905QString jidnick(const QString &jid, const QString &nick)
906{
907 if(nick.isEmpty())
908 return jid;
909 else
910 return nick;
911}
912
913QString logencode(QString str)
914{
915 str.replace(QRegExp("\\\\"), "\\\\"); // backslash to double-backslash
916 str.replace(QRegExp("\\|"), "\\p"); // pipe to \p
917 str.replace(QRegExp("\n"), "\\n"); // newline to \n
918 return str;
919}
920
921QString logdecode(const QString &str)
922{
923 QString ret;
924
925 for(unsigned int n = 0; n < str.length(); ++n) {
926 if(str.at(n) == '\\') {
927 ++n;
928 if(n >= str.length())
929 break;
930
931 if(str.at(n) == 'n')
932 ret.append('\n');
933 if(str.at(n) == 'p')
934 ret.append('|');
935 if(str.at(n) == '\\')
936 ret.append('\\');
937 }
938 else {
939 ret.append(str.at(n));
940 }
941 }
942
943 return ret;
944}
945
946void qstringlistisort(QStringList &c)
947{
948 if ( c.count() <= 1 )
949 return;
950
951 QStringList::Iterator it;
952 uint size = c.count();
953
954 // first, make array that is easy (and quick) to manipulate
955 QString *heap = new QString[ size ];
956
957 int i = 0;
958 for (it = c.begin(); it != c.end(); ++it)
959 heap[i++] = *it;
960
961 // Insertion sort
962 for (uint tmp = 0; tmp < c.count(); tmp++) {
963 heap[tmp] = c[tmp];
964 size = tmp + 1;
965
966 for (uint j = 1; j < size; j++) {
967 QString k = heap[j].lower();
968 QString r = heap[j];
969
970 for (i = j - 1; i >= 0; i--) {
971 if ( QString::compare(k, heap[i].lower()) > 0 )
972 break;
973
974 heap[i+1] = heap[i];
975 }
976
977 heap[i+1] = r;
978 }
979 }
980
981 // now, copy sorted data back to QStringList
982 it = c.begin();
983 for (i = 0; i < (int)size; i++)
984 *it++ = heap[i];
985
986 delete[] heap;
987}
988
989#ifdef Q_WS_PM
990static QString getBrowserSetting (PCSZ key)
991{
992 static const PSZ app = "WPURLDEFAULTSETTINGS";
993
994 QString value;
995 ULONG keyLen = 0;
996
997 if ( PrfQueryProfileSize( HINI_USERPROFILE, app, key, &keyLen ) && keyLen ) {
998 char *buf = new char [keyLen];
999 ULONG realLen =
1000 PrfQueryProfileString( HINI_USERPROFILE, app, key, NULL, buf, keyLen );
1001 if (realLen) {
1002 realLen --; // excude zero terminator
1003 buf[realLen] = 0;
1004 value = QString::fromLocal8Bit(buf);
1005 }
1006 delete[] buf;
1007 }
1008
1009 return value;
1010}
1011#endif
1012
1013void openURL(const QString &url)
1014{
1015 //fprintf(stderr, "openURL: [%s]\n", url.latin1());
1016 bool useCustom = TRUE;
1017
1018 int colon = url.find(':');
1019 if ( colon == -1 )
1020 colon = 0;
1021 QString service = url.left( colon );
1022 if ( service == "jabber" || service == "jid" ) {
1023 // TODO
1024 return;
1025 }
1026
1027#ifdef Q_WS_WIN
1028 if(option.browser == 0)
1029 useCustom = FALSE;
1030#endif
1031#ifdef Q_WS_PM
1032 if(option.browser == 0)
1033 useCustom = FALSE;
1034#endif
1035#ifdef Q_WS_X11
1036 if(option.browser == 0 || option.browser == 2)
1037 useCustom = FALSE;
1038#endif
1039#ifdef Q_WS_MAC
1040 useCustom = FALSE;
1041#endif
1042
1043 if(useCustom) {
1044 bool isMail = FALSE;
1045 QString s = url;
1046 if(url.left(7).lower() == "mailto:") {
1047#ifdef Q_WS_PM
1048 // mozilla/thunderbird doesn't understand e-mails w/o 'mailto:'
1049#else
1050 s.remove(0, 7);
1051#endif
1052 isMail = TRUE;
1053 }
1054
1055 QStringList args;
1056
1057 if(isMail) {
1058 if(option.customMailer.isEmpty()) {
1059 QMessageBox::critical(0, CAP(QObject::tr("URL error")), QObject::tr("Unable to open the URL. You have not selected a mailer (see Options)."));
1060 return;
1061 }
1062 args += QStringList::split(' ', option.customMailer);
1063 }
1064 else {
1065 if(option.customBrowser.isEmpty()) {
1066 QMessageBox::critical(0, CAP(QObject::tr("URL error")), QObject::tr("Unable to open the URL. You have not selected a browser (see Options)."));
1067 return;
1068 }
1069 args += QStringList::split(' ', option.customBrowser);
1070 }
1071
1072 args += s;
1073 QProcess cmd(args);
1074 if(!cmd.start()) {
1075 QMessageBox::critical(0, CAP(QObject::tr("URL error")), QObject::tr("Unable to open the URL. Ensure that your custom browser/mailer exists (see Options)."));
1076 }
1077 }
1078 else {
1079#ifdef Q_WS_WIN
1080 QCString cs = url.local8Bit();
1081 if ((unsigned int)::ShellExecuteA(NULL,NULL,cs.data(),NULL,NULL,SW_SHOW) <= 32) {
1082 QMessageBox::critical(0, CAP(QObject::tr("URL error")), QObject::tr("Unable to open the URL. Ensure that you have a web browser installed."));
1083 }
1084#endif
1085#ifdef Q_WS_PM
1086 QString exe, params, workDir;
1087
1088 if (url.left(7).lower() == "mailto:") {
1089 exe = getBrowserSetting("DefaultMailExe");
1090 params = getBrowserSetting("DefaultMailParameters");
1091 workDir = getBrowserSetting("DefaultMailWorkingDir");
1092 } else if (url.left(5).lower() == "news:") {
1093 exe = getBrowserSetting("DefaultNewsExe");
1094 params = getBrowserSetting("DefaultNewsParameters");
1095 workDir = getBrowserSetting("DefaultNewsWorkingDir");
1096 } else if (url.left(4).lower() == "ftp:") {
1097 exe = getBrowserSetting("DefaultFTPExe");
1098 params = getBrowserSetting("DefaultFTPParameters");
1099 workDir = getBrowserSetting("DefaultFTPWorkingDir");
1100 } else if (url.left(4).lower() == "irc:") {
1101 exe = getBrowserSetting("DefaultIRCExe");
1102 params = getBrowserSetting("DefaultIRCParameters");
1103 workDir = getBrowserSetting("DefaultIRCWorkingDir");
1104 }
1105 // if the specific app is not found, fallback to the general broswer
1106 if (exe.isEmpty()) {
1107 exe = getBrowserSetting("DefaultBrowserExe");
1108 params = getBrowserSetting("DefaultParameters");
1109 workDir = getBrowserSetting("DefaultWorkingDir");
1110 }
1111
1112 bool success = false;
1113
1114 if (!exe.isEmpty()) {
1115 QStringList args;
1116 args += exe;
1117 if (!params.isEmpty()) args += params;
1118 args += url;
1119 QProcess cmd(args);
1120 if (!workDir.isEmpty()) cmd.setWorkingDirectory(workDir);
1121 success = cmd.start();
1122 }
1123
1124 if (!success)
1125 QMessageBox::critical(0, CAP(QObject::tr("URL error")),
1126 QObject::tr("Unable to open the URL. Ensure that you have a web "
1127 "browser installed."));
1128#endif
1129#ifdef Q_WS_X11
1130 // KDE
1131 if(option.browser == 0) {
1132 QStringList args;
1133 args += "kfmclient";
1134 args += "exec";
1135 args += url;
1136 QProcess cmd(args);
1137 if(!cmd.start()) {
1138 QMessageBox::critical(0, CAP(QObject::tr("URL error")), QObject::tr("Unable to open the URL. Ensure that you have KDE installed."));
1139 }
1140 }
1141 // GNOME 2
1142 else if(option.browser == 2) {
1143 QStringList args;
1144 args += "gnome-open";
1145 args += url;
1146 QProcess cmd(args);
1147 if(!cmd.start()) {
1148 QMessageBox::critical(0, CAP(QObject::tr("URL error")), QObject::tr("Unable to open the URL. Ensure that you have GNOME 2 installed."));
1149 }
1150 }
1151#endif
1152#ifdef Q_WS_MAC
1153 // Use Internet Config to hand the URL to the appropriate application, as
1154 // set by the user in the Internet Preferences pane.
1155 // NOTE: ICStart could be called once at Psi startup, saving the
1156 // ICInstance in a global variable, as a minor optimization.
1157 // ICStop should then be called at Psi shutdown if ICStart succeeded.
1158 ICInstance icInstance;
1159 OSType psiSignature = 'psi ';
1160 OSStatus error = ::ICStart( &icInstance, psiSignature );
1161 if ( error == noErr ) {
1162 ConstStr255Param hint( 0x0 );
1163 QCString cs = url.local8Bit();
1164 const char* data = cs.data();
1165 long length = cs.length();
1166 long start( 0 );
1167 long end( length );
1168 // Don't bother testing return value (error); launched application will report problems.
1169 ::ICLaunchURL( icInstance, hint, data, length, &start, &end );
1170 ICStop( icInstance );
1171 }
1172#endif
1173 }
1174}
1175
1176static bool sysinfo_done = FALSE;
1177static int timezone_offset = 0;
1178static QString timezone_str = "N/A";
1179static QString os_str = "Unknown";
1180
1181#if defined(Q_WS_X11) || defined(Q_WS_MAC)
1182#include<time.h>
1183#include<stdlib.h>
1184#include<string.h>
1185#include<sys/utsname.h>
1186#endif
1187
1188static void getSysInfo()
1189{
1190#if defined(Q_WS_X11) || defined(Q_WS_MAC) || defined(Q_WS_PM)
1191 time_t x;
1192 time(&x);
1193 char str[256];
1194 char fmt[32];
1195 strcpy(fmt, "%z");
1196 strftime(str, 256, fmt, localtime(&x));
1197 if(strcmp(fmt, str)) {
1198 QString s = str;
1199 if(s.at(0) == '+')
1200 s.remove(0,1);
1201 s.truncate(s.length()-2);
1202 timezone_offset = s.toInt();
1203 }
1204 strcpy(fmt, "%Z");
1205 strftime(str, 256, fmt, localtime(&x));
1206 if(strcmp(fmt, str))
1207 timezone_str = str;
1208#endif
1209#if defined(Q_WS_X11)
1210 struct utsname u;
1211 uname(&u);
1212 os_str.sprintf("%s", u.sysname);
1213
1214 // get description about os
1215 enum LinuxName {
1216 LinuxNone = 0,
1217
1218 LinuxMandrake,
1219 LinuxDebian,
1220 LinuxRedHat,
1221 LinuxGentoo,
1222 LinuxSlackware,
1223 LinuxSuSE,
1224 LinuxConectiva,
1225 LinuxCaldera,
1226 LinuxLFS,
1227
1228 LinuxASP, // Russian Linux distros
1229 LinuxALT,
1230
1231 LinuxPLD, // Polish Linux distros
1232 LinuxAurox,
1233 LinuxArch
1234 };
1235
1236 enum OsFlags {
1237 OsUseName = 0,
1238 OsUseFile,
1239 OsAppendFile
1240 };
1241
1242 struct OsInfo {
1243 LinuxName id;
1244 OsFlags flags;
1245 QString file;
1246 QString name;
1247 } osInfo[] = {
1248 { LinuxMandrake, OsUseFile, "/etc/mandrake-release", "Mandrake Linux" },
1249 { LinuxDebian, OsAppendFile, "/etc/debian_version", "Debian GNU/Linux" },
1250 { LinuxGentoo, OsUseFile, "/etc/gentoo-release", "Gentoo Linux" },
1251 { LinuxSlackware, OsAppendFile, "/etc/slackware-version", "Slackware Linux" },
1252 { LinuxPLD, OsUseFile, "/etc/pld-release", "PLD Linux" },
1253 { LinuxAurox, OsUseName, "/etc/aurox-release", "Aurox Linux" },
1254 { LinuxArch, OsUseFile, "/etc/arch-release", "Arch Linux" },
1255 { LinuxLFS, OsAppendFile, "/etc/lfs-release", "LFS Linux" },
1256
1257 // untested
1258 { LinuxSuSE, OsUseFile, "/etc/SuSE-release", "SuSE Linux" },
1259 { LinuxConectiva, OsUseFile, "/etc/conectiva-release", "Conectiva Linux" },
1260 { LinuxCaldera, OsUseFile, "/etc/.installed", "Caldera Linux" },
1261
1262 // many distros use the /etc/redhat-release for compatibility, so RedHat will be the last :)
1263 { LinuxRedHat, OsUseFile, "/etc/redhat-release", "RedHat Linux" },
1264
1265 { LinuxNone, OsUseName, "", "" }
1266 };
1267
1268 for (int i = 0; osInfo[i].id != LinuxNone; i++) {
1269 QFileInfo fi( osInfo[i].file );
1270 if ( fi.exists() ) {
1271 QString desc;
1272
1273 QFile f( osInfo[i].file );
1274 f.open( IO_ReadOnly );
1275 f.readLine( desc, 128 );
1276
1277 desc = desc.stripWhiteSpace ();
1278
1279 switch ( osInfo[i].flags ) {
1280 case OsUseFile:
1281 os_str = desc;
1282 break;
1283 case OsUseName:
1284 os_str = osInfo[i].name;
1285 break;
1286 case OsAppendFile:
1287 os_str = osInfo[i].name + " (" + desc + ")";
1288 break;
1289 }
1290
1291 break;
1292 }
1293 }
1294#elif defined(Q_WS_PM)
1295 os_str = "OS/2";
1296#elif defined(Q_WS_MAC)
1297 os_str = "Mac OS X";
1298#endif
1299
1300#if defined(Q_WS_WIN)
1301 TIME_ZONE_INFORMATION i;
1302 //GetTimeZoneInformation(&i);
1303 //timezone_offset = (-i.Bias) / 60;
1304 memset(&i, 0, sizeof(i));
1305 bool inDST = (GetTimeZoneInformation(&i) == TIME_ZONE_ID_DAYLIGHT);
1306 int bias = i.Bias;
1307 if(inDST)
1308 bias += i.DaylightBias;
1309 timezone_offset = (-bias) / 60;
1310 timezone_str = "";
1311 for(int n = 0; n < 32; ++n) {
1312 uint w = inDST ? i.DaylightName[n] : i.StandardName[n];
1313 if(w == 0)
1314 break;
1315 timezone_str += QChar(w);
1316 }
1317
1318 Qt::WindowsVersion v = QApplication::winVersion();
1319 if(v == Qt::WV_95)
1320 os_str = "Windows 95";
1321 else if(v == Qt::WV_98)
1322 os_str = "Windows 98";
1323 else if(v == Qt::WV_Me)
1324 os_str = "Windows Me";
1325 else if(v == Qt::WV_DOS_based)
1326 os_str = "Windows 9x/Me";
1327 else if(v == Qt::WV_NT)
1328 os_str = "Windows NT 4.x";
1329 else if(v == Qt::WV_2000)
1330 os_str = "Windows 2000";
1331 else if(v == Qt::WV_XP)
1332 os_str = "Windows XP";
1333#if QT_VERSION >= 0x030300
1334 else if(v == Qt::WV_2003)
1335 os_str = "Windows Server 2003";
1336#endif
1337 else if(v == Qt::WV_NT_based)
1338 os_str = "Windows NT";
1339#endif
1340 sysinfo_done = TRUE;
1341}
1342
1343QString getOSName()
1344{
1345 if(!sysinfo_done)
1346 getSysInfo();
1347
1348 return os_str;
1349}
1350
1351int getTZOffset()
1352{
1353 if(!sysinfo_done)
1354 getSysInfo();
1355
1356 return timezone_offset;
1357}
1358
1359QString getTZString()
1360{
1361 if(!sysinfo_done)
1362 getSysInfo();
1363
1364 return timezone_str;
1365}
1366
1367
1368#ifdef Q_WS_X11
1369QString getResourcesDir()
1370{
1371 return PSI_DATADIR;
1372}
1373
1374QString getHomeDir()
1375{
1376 QDir proghome(QDir::homeDirPath() + "/.psi");
1377 if(!proghome.exists()) {
1378 QDir home = QDir::home();
1379 home.mkdir(".psi");
1380 chmod(QFile::encodeName(proghome.path()), 0700);
1381 }
1382
1383 return proghome.path();
1384}
1385#endif
1386
1387#ifdef Q_WS_WIN
1388QString getResourcesDir()
1389{
1390#if QT_VERSION >= 0x030200
1391 return qApp->applicationDirPath();
1392#else
1393 char baseName[MAX_PATH];
1394 GetModuleFileNameA(GetModuleHandle(NULL), baseName, MAX_PATH);
1395 QString base(baseName);
1396 int idx = base.findRev('\\');
1397
1398 if (-1 == idx)
1399 return ".";
1400
1401 base.truncate(idx);
1402 QDir baseDir(base);
1403
1404 return baseDir.absPath();
1405#endif
1406}
1407
1408QString getHomeDir()
1409{
1410 QString base;
1411
1412 // Windows 9x
1413 if(QDir::homeDirPath() == QDir::rootDirPath())
1414 base = ".";
1415 // Windows NT/2K/XP variant
1416 else
1417 base = QDir::homeDirPath();
1418
1419 // no trailing slash
1420 if(base.at(base.length()-1) == '/')
1421 base.truncate(base.length()-1);
1422
1423 QDir proghome(base + "/PsiData");
1424 if(!proghome.exists()) {
1425 QDir home(base);
1426 home.mkdir("PsiData");
1427 }
1428
1429 return proghome.path();
1430}
1431#endif
1432
1433#ifdef Q_WS_PM
1434QString getResourcesDir()
1435{
1436#if QT_VERSION >= 0x030200
1437 return qApp->applicationDirPath();
1438#else
1439# error Not implemented!
1440#endif
1441}
1442
1443QString getHomeDir()
1444{
1445 QString base = QDir::homeDirPath();
1446
1447 // no trailing slash
1448 if(base.at(base.length()-1) == '/')
1449 base.truncate(base.length()-1);
1450
1451 QDir proghome(base + "/PsiData");
1452 if(!proghome.exists()) {
1453 QDir home(base);
1454 home.mkdir("PsiData");
1455 }
1456
1457 return proghome.path();
1458}
1459#endif
1460
1461#ifdef Q_WS_MAC
1462/******************************************************************************/
1463/* Get path to Resources directory as a string. */
1464/* Return an empty string if can't find it. */
1465/******************************************************************************/
1466QString getResourcesDir()
1467{
1468 // System routine locates resource files. We "know" that Psi.icns is
1469 // in the Resources directory.
1470 QString resourcePath;
1471 CFBundleRef mainBundle = CFBundleGetMainBundle();
1472 CFStringRef resourceCFStringRef
1473 = CFStringCreateWithCString( NULL, "application.icns",
1474 kCFStringEncodingASCII );
1475 CFURLRef resourceURLRef = CFBundleCopyResourceURL( mainBundle,
1476 resourceCFStringRef,
1477 NULL,
1478 NULL );
1479 if ( resourceURLRef ) {
1480 CFStringRef resourcePathStringRef =
1481 CFURLCopyFileSystemPath( resourceURLRef, kCFURLPOSIXPathStyle );
1482 const char* resourcePathCString =
1483 CFStringGetCStringPtr( resourcePathStringRef, kCFStringEncodingASCII );
1484 if ( resourcePathCString ) {
1485 resourcePath.setLatin1( resourcePathCString );
1486 } else { // CFStringGetCStringPtr failed; use fallback conversion
1487 CFIndex bufferLength = CFStringGetLength( resourcePathStringRef ) + 1;
1488 char* resourcePathCString = new char[ bufferLength ];
1489 Boolean conversionSuccess =
1490 CFStringGetCString( resourcePathStringRef,
1491 resourcePathCString, bufferLength,
1492 kCFStringEncodingASCII );
1493 if ( conversionSuccess ) {
1494 resourcePath = resourcePathCString;
1495 }
1496 delete [] resourcePathCString; // I own this
1497 }
1498 CFRelease( resourcePathStringRef ); // I own this
1499 }
1500 // Remove the tail component of the path
1501 if ( ! resourcePath.isNull() ) {
1502 QFileInfo fileInfo( resourcePath );
1503 resourcePath = fileInfo.dirPath( true );
1504 }
1505 return resourcePath;
1506}
1507
1508QString getHomeDir()
1509{
1510 QDir proghome(QDir::homeDirPath() + "/.psi");
1511 if(!proghome.exists()) {
1512 QDir home = QDir::home();
1513 home.mkdir(".psi");
1514 chmod(QFile::encodeName(proghome.path()), 0700);
1515 }
1516
1517 return proghome.path();
1518}
1519#endif
1520
1521
1522QString getHistoryDir()
1523{
1524 QDir history(pathToProfile(activeProfile) + "/history");
1525 if (!history.exists()) {
1526 QDir profile(pathToProfile(activeProfile));
1527 profile.mkdir("history");
1528 }
1529
1530 return history.path();
1531}
1532
1533QString getVCardDir()
1534{
1535 QDir vcard(pathToProfile(activeProfile) + "/vcard");
1536 if (!vcard.exists()) {
1537 QDir profile(pathToProfile(activeProfile));
1538 profile.mkdir("vcard");
1539 }
1540
1541 return vcard.path();
1542}
1543
1544bool fileCopy(const QString &src, const QString &dest)
1545{
1546 QFile in(src);
1547 QFile out(dest);
1548
1549 if(!in.open(IO_ReadOnly))
1550 return FALSE;
1551 if(!out.open(IO_WriteOnly))
1552 return FALSE;
1553
1554 char *dat = new char[16384];
1555 int n = 0;
1556 while(!in.atEnd()) {
1557 n = in.readBlock(dat, 16384);
1558 if(n == -1) {
1559 delete dat;
1560 return FALSE;
1561 }
1562 out.writeBlock(dat, n);
1563 }
1564 delete dat;
1565
1566 out.close();
1567 in.close();
1568
1569 return TRUE;
1570}
1571
1572class BeepThread : public QThread
1573{
1574public:
1575 BeepThread() : sem(1), terminate(false) {}
1576 ~BeepThread() { while (sem.available() != sem.total()) sem--; }
1577 void run() {
1578 lock.lock();
1579 while (!terminate) {
1580 while (!terminate && queue.count()) {
1581 Beep beep = queue.front();
1582 queue.pop_front();
1583 lock.unlock();
1584#if defined(Q_WS_WIN)
1585 if (beep.freq && beep.dur)
1586 ::Beep(beep.freq, beep.dur);
1587 else
1588#elif defined(Q_WS_PM)
1589 if (beep.freq && beep.dur)
1590 DosBeep(beep.freq, beep.dur);
1591 else
1592#endif
1593 QApplication::beep();
1594 lock.lock();
1595 }
1596 lock.unlock();
1597 sem++;
1598 lock.lock();
1599 }
1600 lock.unlock();
1601 }
1602 void beginSequence() {
1603 lock.lock();
1604 queue.clear(); // stop the previous sequence
1605 }
1606 void addNote(ulong freq, ulong dur) {
1607 queue.push_back(Beep(freq, dur));
1608 }
1609 void endSequence() {
1610 lock.unlock();
1611 if (!sem.available())
1612 sem--;
1613 }
1614 void stop() {
1615 lock.lock();
1616 terminate = true;
1617 lock.unlock();
1618 if (!sem.available())
1619 sem--;
1620 wait();
1621 }
1622private:
1623 struct Beep {
1624 Beep() : freq(0), dur(0) {}
1625 Beep(ulong f, ulong d) : freq(f), dur(d) {}
1626 const ulong freq;
1627 const ulong dur;
1628 };
1629 QMutex lock;
1630 QSemaphore sem; // not QWaitCond cause we need level-triggered semantics
1631 QValueList<Beep> queue;
1632 bool terminate;
1633};
1634
1635static BeepThread *beepThread = 0;
1636
1637static void cleanupBeepThread()
1638{
1639 if (beepThread) {
1640 beepThread->stop();
1641 delete beepThread;
1642 beepThread = 0;
1643 }
1644}
1645
1646void soundPlay(const QString &str)
1647{
1648 if (str.startsWith("!beep")) {
1649 QStringList seq = QStringList::split(':',str);
1650 if (seq[0].length() == 5 /* strlen("!beep") */) {
1651 if (beepThread == 0) {
1652 beepThread = new BeepThread();
1653 if (beepThread) {
1654 qAddPostRoutine(cleanupBeepThread);
1655 beepThread->start();
1656 }
1657 }
1658 Q_ASSERT(beepThread);
1659 if (beepThread) {
1660 beepThread->beginSequence();
1661#if defined(Q_WS_WIN) || defined(Q_WS_PM)
1662 ulong lastDur = 100;
1663 size_t i = 1;
1664 for (; i < seq.count(); ++i) {
1665 QStringList note = QStringList::split(',',seq[i]);
1666 if (note.count() < 1) break;
1667 ulong freq = note[0].toULong();
1668 if (freq < 0x25 || freq > 0x7FFF) break;
1669 ulong dur = lastDur;
1670 if (note.count() > 2) break;
1671 if (note.count() == 2) {
1672 dur = note[1].toULong();
1673 // 3s is the reasonable maximum for the duration
1674 if (dur == 0 || dur > 3000) break;
1675 lastDur = dur;
1676 }
1677 beepThread->addNote(freq, dur);
1678 }
1679 if (i == 1) // didn't find a single note
1680#endif
1681 beepThread->addNote(0, 0);
1682 beepThread->endSequence();
1683 }
1684 return;
1685 }
1686 }
1687
1688 if(!QFile::exists(str))
1689 return;
1690
1691#if defined(Q_WS_WIN) || defined(Q_WS_PM) || defined(Q_WS_MAC)
1692 QSound::play(str);
1693#else
1694 if(!option.player.isEmpty()) {
1695 QStringList args;
1696 args = QStringList::split(' ', option.player);
1697 args += str;
1698 QProcess cmd(args);
1699 if(!cmd.start())
1700 wait3(NULL,WNOHANG,NULL);
1701 }
1702#endif
1703}
1704
1705XMPP::Status makeStatus(int x, const QString &str)
1706{
1707 XMPP::Status s;
1708 s.setStatus(str);
1709 if(x == STATUS_OFFLINE)
1710 s.setIsAvailable(false);
1711 else if(x == STATUS_INVISIBLE)
1712 s.setIsInvisible(true);
1713 else {
1714 if(x == STATUS_AWAY)
1715 s.setShow("away");
1716 else if(x == STATUS_XA)
1717 s.setShow("xa");
1718 else if(x == STATUS_DND)
1719 s.setShow("dnd");
1720 else if(x == STATUS_CHAT)
1721 s.setShow("chat");
1722 }
1723
1724 return s;
1725}
1726
1727int makeSTATUS(const XMPP::Status &s)
1728{
1729 int type = STATUS_ONLINE;
1730 if(!s.isAvailable())
1731 type = STATUS_OFFLINE;
1732 else if(s.isInvisible())
1733 type= STATUS_INVISIBLE;
1734 else {
1735 if(s.show() == "away")
1736 type = STATUS_AWAY;
1737 else if(s.show() == "xa")
1738 type = STATUS_XA;
1739 else if(s.show() == "dnd")
1740 type = STATUS_DND;
1741 else if(s.show() == "chat")
1742 type = STATUS_CHAT;
1743 }
1744 return type;
1745}
1746
1747#include<qlayout.h>
1748QLayout *rw_recurseFindLayout(QLayout *lo, QWidget *w)
1749{
1750 //printf("scanning layout: %p\n", lo);
1751 QLayoutIterator it = lo->iterator();
1752 for(QLayoutItem *i; (i = it.current()); ++it) {
1753 //printf("found: %p,%p\n", i->layout(), i->widget());
1754 QLayout *slo = i->layout();
1755 if(slo) {
1756 QLayout *tlo = rw_recurseFindLayout(slo, w);
1757 if(tlo)
1758 return tlo;
1759 }
1760 else if(i->widget() == w)
1761 return lo;
1762 }
1763 return 0;
1764}
1765
1766QLayout *rw_findLayoutOf(QWidget *w)
1767{
1768 return rw_recurseFindLayout(w->parentWidget()->layout(), w);
1769}
1770
1771void replaceWidget(QWidget *a, QWidget *b)
1772{
1773 if(!a)
1774 return;
1775
1776 QLayout *lo = rw_findLayoutOf(a);
1777 if(!lo)
1778 return;
1779 //printf("decided on this: %p\n", lo);
1780
1781 if(lo->inherits("QBoxLayout")) {
1782 QBoxLayout *bo = (QBoxLayout *)lo;
1783 int n = bo->findWidget(a);
1784 bo->insertWidget(n+1, b);
1785 delete a;
1786 }
1787}
1788
1789void closeDialogs(QWidget *w)
1790{
1791 // close qmessagebox?
1792 const QObjectList *list = w->children();
1793 if(list) {
1794 QObjectListIt it(*list);
1795 QPtrList<QDialog> dialogs;
1796 for(QObject *o; (o = it.current()); ++it) {
1797 if(o->inherits("QDialog"))
1798 dialogs.append((QDialog *)o);
1799 }
1800 QPtrListIterator<QDialog> dit(dialogs);
1801 for(QDialog *w; (w = dit.current()); ++dit)
1802 w->close();
1803 }
1804}
1805
1806QString enc822jid(const QString &s)
1807{
1808 QString out;
1809 for(int n = 0; n < (int)s.length(); ++n) {
1810 if(s[n] == '\\' || s[n] == '<' || s[n] == '>') {
1811 QString hex;
1812 hex.sprintf("\\x%02X", (unsigned char )s[n]);
1813 out.append(hex);
1814 }
1815 else
1816 out += s[n];
1817 }
1818 return out;
1819}
1820
1821QString dec822jid(const QString &s)
1822{
1823 QString out;
1824 for(int n = 0; n < (int)s.length(); ++n) {
1825 if(s[n] == '\\' && n + 3 < (int)s.length()) {
1826 int x = n + 1;
1827 n += 3;
1828 if(s[x] != 'x')
1829 continue;
1830 ushort val = 0;
1831 val += hexChar2int(s[x+1])*16;
1832 val += hexChar2int(s[x+2]);
1833 QChar c(val);
1834 out += c;
1835 }
1836 else
1837 out += s[n];
1838 }
1839 return out;
1840}
1841
1842#ifdef Q_WS_X11
1843#include<X11/Xlib.h>
1844#include<X11/Xutil.h> // needed for WM_CLASS hinting
1845
1846void x11wmClass(Display *dsp, WId wid, QString resName)
1847{
1848 char app_name[] = "psi";
1849
1850 //Display *dsp = x11Display(); // get the display
1851 //WId win = winId(); // get the window
1852 XClassHint classhint; // class hints
1853 classhint.res_name = (char *)resName.latin1(); // res_name
1854 classhint.res_class = app_name; // res_class
1855 XSetClassHint(dsp, wid, &classhint); // set the class hints
1856}
1857
1858//>>>-- Nathaniel Gray -- Caltech Computer Science ------>
1859//>>>-- Mojave Project -- http://mojave.cs.caltech.edu -->
1860// Copied from http://www.nedit.org/archives/discuss/2002-Aug/0386.html
1861
1862// Helper function
1863bool getCardinal32Prop(Display *display, Window win, char *propName, long *value)
1864{
1865 Atom nameAtom, typeAtom, actual_type_return;
1866 int actual_format_return, result;
1867 unsigned long nitems_return, bytes_after_return;
1868 long *result_array=NULL;
1869
1870 nameAtom = XInternAtom(display, propName, False);
1871 typeAtom = XInternAtom(display, "CARDINAL", False);
1872 if (nameAtom == None || typeAtom == None) {
1873 //qDebug("Atoms not interned!");
1874 return false;
1875 }
1876
1877
1878 // Try to get the property
1879 result = XGetWindowProperty(display, win, nameAtom, 0, 1, False,
1880 typeAtom, &actual_type_return, &actual_format_return,
1881 &nitems_return, &bytes_after_return,
1882 (unsigned char **)&result_array);
1883
1884 if( result != Success ) {
1885 //qDebug("not Success");
1886 return false;
1887 }
1888 if( actual_type_return == None || actual_format_return == 0 ) {
1889 //qDebug("Prop not found");
1890 return false;
1891 }
1892 if( actual_type_return != typeAtom ) {
1893 //qDebug("Wrong type atom");
1894 }
1895 *value = result_array[0];
1896 XFree(result_array);
1897 return true;
1898}
1899
1900
1901// Get the desktop number that a window is on
1902bool desktopOfWindow(Window *window, long *desktop)
1903{
1904 Display *display = qt_xdisplay();
1905 bool result = getCardinal32Prop(display, *window, (char *)"_NET_WM_DESKTOP", desktop);
1906 //if( result )
1907 // qDebug("Desktop: " + QString::number(*desktop));
1908 return result;
1909}
1910
1911
1912// Get the current desktop the WM is displaying
1913bool currentDesktop(long *desktop)
1914{
1915 Window rootWin;
1916 Display *display = qt_xdisplay();;
1917 bool result;
1918
1919 rootWin = RootWindow(qt_xdisplay(), XDefaultScreen(qt_xdisplay()));
1920 result = getCardinal32Prop(display, rootWin, (char *)"_NET_CURRENT_DESKTOP", desktop);
1921 //if( result )
1922 // qDebug("Current Desktop: " + QString::number(*desktop));
1923 return result;
1924}
1925#endif
1926
1927void bringToFront(QWidget *w, bool grabFocus /* = true */)
1928{
1929#if defined(Q_WS_X11)
1930 // If we're not on the current desktop, do the hide/show trick
1931 long dsk, curr_dsk;
1932 Window win = w->winId();
1933 if(desktopOfWindow(&win, &dsk) && currentDesktop(&curr_dsk)) {
1934 if(dsk != curr_dsk) {
1935 w->hide();
1936 //qApp->processEvents();
1937 }
1938 }
1939#else
1940 // FIXME: multi-desktop hacks for Win and Mac required
1941#endif
1942
1943 w->show();
1944 if(w->isMinimized()) {
1945 //w->hide();
1946 if(w->isMaximized())
1947 w->showMaximized();
1948 else
1949 w->showNormal();
1950 }
1951
1952#ifdef Q_WS_WIN
1953 w->raise();
1954 if (grabFocus) {
1955 w->setActiveWindow();
1956 } else {
1957 if (GetTopWindow(NULL) != w->winId()) {
1958 // On Win32, raise() cannot move a window on top of the currently
1959 // active window w/o making the former active. To overcome this,
1960 // we use a trick by first making the window floating on top and
1961 // then restoring the normal behavior.
1962 SetWindowPos(w->winId(), HWND_TOPMOST, 0, 0, 0, 0,
1963 SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE);
1964 SetWindowPos(w->winId(), HWND_NOTOPMOST, 0, 0, 0, 0,
1965 SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE);
1966 }
1967 }
1968#else
1969 if(grabFocus)
1970 w->setActiveWindow();
1971 else
1972 w->raise();
1973#endif
1974
1975#if defined(Q_WS_PM)
1976 // When the widget is totally outside the screen bounds after making it shown,
1977 // we assume it's on a different (invisible) XPager's page. The assumption is
1978 // not so safe, but is there any other way to determine XPager is in action?..
1979 QRect geo = w->frameGeometry();
1980 QRect scrGeo = qApp->desktop()->screenGeometry();
1981 if (!w->isMinimized() && !scrGeo.contains(geo)) {
1982 // We assume that XPager stores invisible pages in the coordinate space
1983 // beyond (0,0)x(screen_width,screen_height) wiht a 8 px gap between
1984 // pages. Move it to the visible page.
1985 int sw = scrGeo.width() + 8;
1986 int sh = scrGeo.height() + 8;
1987 int x = geo.x() % sw;
1988 if (x < 0) x = sw + x;
1989 int y = geo.y() % sh;
1990 if (y < 0) y = sh + y;
1991 w->move (x, y);
1992 }
1993#endif
1994}
1995
1996bool operator!=(const QMap<QString, QString> &m1, const QMap<QString, QString> &m2)
1997{
1998 if ( m1.size() != m2.size() )
1999 return true;
2000
2001 QMap<QString, QString>::ConstIterator it = m1.begin(), it2;
2002 for ( ; it != m1.end(); ++it) {
2003 it2 = m2.find( it.key() );
2004 if ( it2 == m2.end() )
2005 return true;
2006 if ( it.data() != it2.data() )
2007 return true;
2008 }
2009
2010 return false;
2011}
2012
Note: See TracBrowser for help on using the repository browser.