/* * common.cpp - contains all the common variables and functions for Psi * Copyright (C) 2001-2003 Justin Karneges * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ #include"common.h" #if defined(Q_OS_OS2) && !defined(QT_OS2_NO_SYSEXCEPTIONS) #include #endif #include"profiles.h" #include"rtparse.h" #include"psievent.h" QString PROG_NAME = "Psi/2"; //QString PROG_VERSION = "0.10.2"; QString PROG_VERSION = "0.10.2-dev (" __DATE__ ")"; #ifdef HAVE_CONFIG #include "config.h" #endif #ifndef PSI_DATADIR #define PSI_DATADIR "/usr/local/share/psi" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef Q_WS_X11 #include #include #include #endif #ifdef Q_WS_WIN #include #endif #ifdef Q_WS_PM #include #include #endif #ifdef Q_WS_MAC #include #include #include // for HIToolbox/InternetConfig #include #endif QString activeProfile; //QStringList dtcp_hostList; //int dtcp_port; //QString dtcp_proxy; //bool link_test = false; bool use_gpg = true; bool no_gpg_agent = false; uint psi_dialog_flags = (Qt::WStyle_SysMenu | Qt::WStyle_MinMax); //uint psi_dialog_flags = (Qt::WStyle_Customize | Qt::WStyle_DialogBorder | Qt::WStyle_Title | Qt::WStyle_SysMenu | Qt::WStyle_MinMax); char *pixdat_ft_back, *pixdat_ft_file, *pixdat_ft_folder; int pixlen_ft_back, pixlen_ft_file, pixlen_ft_folder; XMPP::Client *debug_jabber = 0; Options option; PsiGlobal g; PsiIconset *is; bool useSound; QString qstrlower(QString str) { for(unsigned int n = 0; n < str.length(); ++n) { str.at(n) = str.at(n).lower(); } return str; } int qstrcasecmp(const QString &str1, const QString &str2) { if(str1.length() != str2.length()) return 1; for(unsigned int n = 0; n < str1.length(); ++n) { if(str1.at(n).lower() != str2.at(n).lower()) return 1; } return 0; } int qstringlistmatch(QStringList &list, const QString &str) { int n = 0; for(QStringList::Iterator i = list.begin(); i != list.end(); ++i, ++n) { if(*i == str) return n; } return -1; } QString qstringlistlookup(QStringList &list, int x) { int n = 0; QStringList::Iterator i = list.begin(); for(;i != list.end() && n < x; ++i, ++n); if(n != x) return ""; return *i; } QString CAP(const QString &str) { return QString("%1: %2").arg(PROG_NAME).arg(str); } QString eatptag(QString txt) { if(txt.left(3) == "

") txt = txt.mid(3, txt.length() - 7); // strlen("

") + strlen("

") = 7 return txt; } QString qstrquote(const QString &toquote, int width, bool quoteEmpty) { int ql = 0, col = 0, atstart = 1, ls=0; QString quoted = "> "+toquote; // quote first line QString rxs = quoteEmpty ? "\n" : "\n(?!\\s*\n)"; QRegExp rx(rxs); // quote following lines quoted.replace(rx, "\n> "); rx.setPattern("> +>"); // compress > > > > quotes to >>>> quoted.replace(rx, ">>"); quoted.replace(rx, ">>"); quoted.replace(QRegExp(" +\n"), "\n"); // remove trailing spaces if (!quoteEmpty) { quoted.replace(QRegExp("^>+\n"), "\n\n"); // unquote empty lines quoted.replace(QRegExp("\n>+\n"), "\n\n"); } for (int i=0;i<(int) quoted.length();i++) { col++; if (atstart && quoted[i] == '>') ql++; else atstart=0; switch(quoted[i].latin1()) { case '\n': ql = col = 0; atstart = 1; break; case ' ': case '\t': ls = i; break; } if (quoted[i]=='\n') { ql=0; atstart = 1; } if (col > width) { if ((ls+width) < i) { ls = i; i = quoted.length(); while ((ls', ql)); i += ql+1; col = 0; } } } quoted += "\n\n";// add two empty lines to quoted text - the cursor // will be positioned at the end of those. return quoted; } QString plain2rich(const QString &plain) { QString rich; int col = 0; for(int i = 0; i < (int)plain.length(); ++i) { if(plain[i] == '\n') { rich += "
"; col = 0; } else if(plain[i] == '\t') { rich += QChar::nbsp; while(col % 4) { rich += QChar::nbsp; ++col; } } else if(plain[i].isSpace()) { if(i > 0 && plain[i-1] == ' ') rich += QChar::nbsp; else rich += ' '; } else if(plain[i] == '<') rich += "<"; else if(plain[i] == '>') rich += ">"; else if(plain[i] == '\"') rich += """; else if(plain[i] == '\'') rich += "'"; else if(plain[i] == '&') rich += "&"; else rich += plain[i]; ++col; } return rich; } QString rich2plain(const QString &in) { QString out; for(int i = 0; i < (int)in.length(); ++i) { // tag? if(in[i] == '<') { // find end of tag ++i; int n = in.find('>', i); if(n == -1) break; QString str = in.mid(i, (n-i)); i = n; QString tagName; n = str.find(' '); if(n != -1) tagName = str.mid(0, n); else tagName = str; if(tagName == "br") out += '\n'; } // entity? else if(in[i] == '&') { // find a semicolon ++i; int n = in.find(';', i); if(n == -1) break; QString type = in.mid(i, (n-i)); i = n; // should be n+1, but we'll let the loop increment do it if(type == "amp") out += '&'; else if(type == "lt") out += '<'; else if(type == "gt") out += '>'; else if(type == "quot") out += '\"'; else if(type == "apos") out += '\''; } else if(in[i].isSpace()) { if(in[i] == QChar::nbsp) out += ' '; else if(in[i] != '\n') { if(i == 0) out += ' '; else { QChar last = out.at(out.length()-1); bool ok = TRUE; if(last.isSpace() && last != '\n') ok = FALSE; if(ok) out += ' '; } } } else { out += in[i]; } } return out; } // clips plain text QString clipStatus(const QString &str, int width, int height) { QString out = ""; int at = 0; int len = str.length(); if(len == 0) return out; // only take the first "height" lines for(int n2 = 0; n2 < height; ++n2) { // only take the first "width" chars QString line; bool hasNewline = false; for(int n = 0; at < len; ++n, ++at) { if(str.at(at) == '\n') { hasNewline = true; break; } line += str.at(at); } ++at; if((int)line.length() > width) { line.truncate(width-3); line += "..."; } out += line; if(hasNewline) out += '\n'; if(at >= len) break; } return out; } QString expandEntities(const QString &in) { return QStyleSheet::escape(in); } QString resolveEntities(const QString &in) { QString out; for(int i = 0; i < (int)in.length(); ++i) { if(in[i] == '&') { // find a semicolon ++i; int n = in.find(';', i); if(n == -1) break; QString type = in.mid(i, (n-i)); i = n; // should be n+1, but we'll let the loop increment do it if(type == "amp") out += '&'; else if(type == "lt") out += '<'; else if(type == "gt") out += '>'; else if(type == "quot") out += '\"'; else if(type == "apos") out += '\''; } else { out += in[i]; } } return out; } static bool linkify_pmatch(const QString &str1, int at, const QString &str2) { if(str2.length() > (str1.length()-at)) return FALSE; for(int n = 0; n < (int)str2.length(); ++n) { if(str1.at(n+at).lower() != str2.at(n).lower()) return FALSE; } return TRUE; } static bool linkify_isOneOf(const QChar &c, const QString &charlist) { for(int i = 0; i < (int)charlist.length(); ++i) { if(c == charlist.at(i)) return TRUE; } return FALSE; } // encodes a few dangerous html characters static QString linkify_htmlsafe(const QString &in) { QString out; for(unsigned int n = 0; n < in.length(); ++n) { if(linkify_isOneOf(in.at(n), "\"\'`<>")) { // hex encode QString hex; hex.sprintf("%%%02X", in.at(n).latin1()); out.append(hex); } else { out.append(in.at(n)); } } return out; } static bool linkify_okUrl(const QString &url) { if(url.at(url.length()-1) == '.') return FALSE; return TRUE; } static bool linkify_okEmail(const QString &addy) { // this makes sure that there is an '@' and a '.' after it, and that there is // at least one char for each of the three sections int n = addy.find('@'); if(n == -1 || n == 0) return FALSE; int d = addy.find('.', n+1); if(d == -1 || d == 0) return FALSE; if((addy.length()-1) - d <= 0) return FALSE; if(addy.find("..") != -1) return false; return TRUE; } QString linkify(const QString &in) { QString out = in; int x1, x2; bool isUrl, isEmail; QString linked, link, href; for(int n = 0; n < (int)out.length(); ++n) { isUrl = FALSE; isEmail = FALSE; x1 = n; if(linkify_pmatch(out, n, "http://")) { n += 7; isUrl = TRUE; href = ""; } else if(linkify_pmatch(out, n, "https://")) { n += 8; isUrl = TRUE; href = ""; } else if(linkify_pmatch(out, n, "ftp://")) { n += 6; isUrl = TRUE; href = ""; } else if(linkify_pmatch(out, n, "news://")) { n += 7; isUrl = TRUE; href = ""; } else if (linkify_pmatch(out, n, "ed2k://")) { n += 7; isUrl = TRUE; href = ""; } else if(linkify_pmatch(out, n, "www.")) { isUrl = TRUE; href = "http://"; } else if(linkify_pmatch(out, n, "ftp.")) { isUrl = TRUE; href = "ftp://"; } else if(linkify_pmatch(out, n, "@")) { isEmail = TRUE; href = "mailto:"; } if(isUrl) { // make sure the previous char is not alphanumeric if(x1 > 0 && out.at(x1-1).isLetterOrNumber()) continue; // find whitespace (or end) for(x2 = n; x2 < (int)out.length(); ++x2) { if(out.at(x2).isSpace() || out.at(x2) == '<') break; } int len = x2-x1; QString pre = resolveEntities(out.mid(x1, x2-x1)); // go backward hacking off unwanted punctuation int cutoff; for(cutoff = pre.length()-1; cutoff >= 0; --cutoff) { if(!linkify_isOneOf(pre.at(cutoff), "!?,.()[]{}<>\"")) break; } ++cutoff; //++x2; link = pre.mid(0, cutoff); if(!linkify_okUrl(link)) { n = x1 + link.length(); continue; } href += link; href = linkify_htmlsafe(href); //printf("link: [%s], href=[%s]\n", link.latin1(), href.latin1()); linked = QString("").arg(href) + expandEntities(link) + "" + expandEntities(pre.mid(cutoff)); out.replace(x1, len, linked); n = x1 + linked.length() - 1; } else if(isEmail) { // go backward till we find the beginning if(x1 == 0) continue; --x1; for(; x1 >= 0; --x1) { if(!linkify_isOneOf(out.at(x1), "_.-") && !out.at(x1).isLetterOrNumber()) break; } ++x1; // go forward till we find the end x2 = n + 1; for(; x2 < (int)out.length(); ++x2) { if(!linkify_isOneOf(out.at(x2), "_.-") && !out.at(x2).isLetterOrNumber()) break; } int len = x2-x1; link = out.mid(x1, len); //link = resolveEntities(link); if(!linkify_okEmail(link)) { n = x1 + link.length(); continue; } href += link; //printf("link: [%s], href=[%s]\n", link.latin1(), href.latin1()); linked = QString("").arg(href) + link + ""; out.replace(x1, len, linked); n = x1 + linked.length() - 1; } } return out; } // sickening QString emoticonify(const QString &in) { RTParse p(in); while ( !p.atEnd() ) { // returns us the first chunk as a plaintext string QString str = p.next(); int i = 0; while ( i >= 0 ) { // find closest emoticon int ePos = -1; Icon *closest = 0; int foundPos = -1, foundLen = -1; QPtrListIterator iconsets(is->emoticons); Iconset *iconset; while ( (iconset = iconsets.current()) != 0 ) { QPtrListIterator it = iconset->iterator(); for ( ; it.current(); ++it) { Icon *icon = it.current(); if ( icon->regExp().isEmpty() ) continue; // some hackery int iii = i; bool searchAgain; do { searchAgain = false; // find the closest match const QRegExp &rx = icon->regExp(); int n = rx.search(str, iii); if ( n == -1 ) continue; if(ePos == -1 || n < ePos || (rx.matchedLength() > foundLen && n < ePos + foundLen)) { // there must be whitespace at least on one side of the emoticon if ( ( n == 0 ) || ( n+rx.matchedLength() == (int)str.length() ) || ( n > 0 && str[n-1].isSpace() ) || ( n+rx.matchedLength() < (int)str.length() && str[n+rx.matchedLength()].isSpace() ) ) { ePos = n; closest = icon; foundPos = n; foundLen = rx.matchedLength(); break; } searchAgain = true; } iii = n + rx.matchedLength(); } while ( searchAgain ); } ++iconsets; } QString s; if(ePos == -1) s = str.mid(i); else s = str.mid(i, ePos-i); p.putPlain(s); if ( !closest ) break; p.putRich( QString("").arg(closest->name()).arg(str.mid(foundPos, foundLen)) ); i = foundPos + foundLen; } } QString out = p.output(); //enable *bold* stuff // //old code //out=out.replace(QRegExp("(^[^<>\\s]*|\\s[^<>\\s]*)\\*(\\S+)\\*([^<>\\s]*\\s|[^<>\\s]*$)"),"\\1*\\2*\\3"); //out=out.replace(QRegExp("(^[^<>\\s\\/]*|\\s[^<>\\s\\/]*)\\/([^\\/\\s]+)\\/([^<>\\s\\/]*\\s|[^<>\\s\\/]*$)"),"\\1/\\2/\\3"); //out=out.replace(QRegExp("(^[^<>\\s]*|\\s[^<>\\s]*)_(\\S+)_([^<>\\s]*\\s|[^<>\\s]*$)"),"\\1_\\2_\\3"); out=out.replace(QRegExp("(^_|\\s_)(\\S+)(_\\s|_$)"),"\\1\\2\\3"); out=out.replace(QRegExp("(^\\*|\\s\\*)(\\S+)(\\*\\s|\\*$)"),"\\1\\2\\3"); out=out.replace(QRegExp("(^\\/|\\s\\/)(\\S+)(\\/\\s|\\/$)"),"\\1\\2\\3"); return out; } QString encodePassword(const QString &pass, const QString &key) { QString result; unsigned int n1, n2; if(key.length() == 0) return pass; for(n1 = 0, n2 = 0; n1 < pass.length(); ++n1) { ushort x = pass.at(n1).unicode() ^ key.at(n2++).unicode(); QString hex; hex.sprintf("%04x", x); result += hex; if(n2 >= key.length()) n2 = 0; } return result; } QString decodePassword(const QString &pass, const QString &key) { QString result; unsigned int n1, n2; if(key.length() == 0) return pass; for(n1 = 0, n2 = 0; n1 < pass.length(); n1 += 4) { ushort x = 0; if(n1 + 4 > pass.length()) break; x += hexChar2int(pass.at(n1))*4096; x += hexChar2int(pass.at(n1+1))*256; x += hexChar2int(pass.at(n1+2))*16; x += hexChar2int(pass.at(n1+3)); QChar c(x ^ key.at(n2++).unicode()); result += c; if(n2 >= key.length()) n2 = 0; } return result; } QString status2txt(int status) { switch(status) { case STATUS_OFFLINE: return QObject::tr("Offline"); case STATUS_AWAY: return QObject::tr("Away"); case STATUS_XA: return QObject::tr("Not Available"); case STATUS_DND: return QObject::tr("Do not Disturb"); case STATUS_CHAT: return QObject::tr("Free for Chat"); case STATUS_INVISIBLE: return QObject::tr("Invisible"); case STATUS_ONLINE: default: return QObject::tr("Online"); } } Icon category2icon(const QString &category, const QString &type) { // TODO: update this to http://www.jabber.org/registrar/disco-categories.html#gateway // still have to add more options... if ( category == "service" || category == "gateway" ) { QString trans; if (type == "aim") trans = "aim"; else if (type == "icq") trans = "icq"; else if (type == "msn") trans = "msn"; else if (type == "yahoo") trans = "yahoo"; else if (type == "gadu-gadu" || type == "x-gadugadu") trans = "gadugadu"; else if (type == "sms") trans = "sms"; else trans = "transport"; return is->transportStatus(trans, STATUS_ONLINE); // irc // jud // pager // jabber // serverlist // smtp } else if ( category == "conference" ) { if (type == "public" || type == "private" || type == "text" || type == "irc") return IconsetFactory::icon("psi/groupChat"); else if (type == "url") return IconsetFactory::icon("psi/www"); // irc // list // topic } else if ( category == "validate" ) { if (type == "xml") return IconsetFactory::icon("psi/xml"); // grammar // spell } else if ( category == "user" || category == "client" ) { // client // forward // inbox // portable // voice return is->status(STATUS_ONLINE); } // application // bot // calendar // editor // fileserver // game // whiteboard // headline // logger // notice // rss // stock // keyword // dictionary // dns // software // thesaurus // web // whois // render // en2ru // ??2?? // tts return Icon(); } int hexChar2int(char c) { if(c >= 'A' && c <= 'F') return c - 'A' + 10; else if(c >= 'a' && c <= 'f') return c - 'a' + 10; else if(c >= '0' && c <= '9') return c - '0'; return 0; } char int2hexChar(int x) { if(x < 10) return (char)x + '0'; else return (char)x - 10 + 'a'; } QString jidEncode(const QString &jid) { QString jid2; for(unsigned int n = 0; n < jid.length(); ++n) { if(jid.at(n) == '@') { jid2.append("_at_"); } else if(jid.at(n) == '.') { jid2.append('.'); } else if(!jid.at(n).isLetterOrNumber()) { // hex encode QString hex; hex.sprintf("%%%02X", jid.at(n).latin1()); jid2.append(hex); } else { jid2.append(jid.at(n)); } } return jid2; } QString jidDecode(const QString &jid) { QString jid2; int n; for(n = 0; n < (int)jid.length(); ++n) { if(jid.at(n) == '%' && (jid.length() - n - 1) >= 2) { QString str = jid.mid(n+1,2); bool ok; char c = str.toInt(&ok, 16); if(!ok) continue; QChar a(c); jid2.append(a); n += 2; } else { jid2.append(jid.at(n)); } } // search for the _at_ backwards, just in case for(n = (int)jid2.length(); n >= 3; --n) { if(jid2.mid(n, 4) == "_at_") { jid2.replace(n, 4, "@"); break; } } return jid2; } QString jidnick(const QString &jid, const QString &nick) { if(nick.isEmpty()) return jid; else return nick; } QString logencode(QString str) { str.replace(QRegExp("\\\\"), "\\\\"); // backslash to double-backslash str.replace(QRegExp("\\|"), "\\p"); // pipe to \p str.replace(QRegExp("\n"), "\\n"); // newline to \n return str; } QString logdecode(const QString &str) { QString ret; for(unsigned int n = 0; n < str.length(); ++n) { if(str.at(n) == '\\') { ++n; if(n >= str.length()) break; if(str.at(n) == 'n') ret.append('\n'); if(str.at(n) == 'p') ret.append('|'); if(str.at(n) == '\\') ret.append('\\'); } else { ret.append(str.at(n)); } } return ret; } void qstringlistisort(QStringList &c) { if ( c.count() <= 1 ) return; QStringList::Iterator it; uint size = c.count(); // first, make array that is easy (and quick) to manipulate QString *heap = new QString[ size ]; int i = 0; for (it = c.begin(); it != c.end(); ++it) heap[i++] = *it; // Insertion sort for (uint tmp = 0; tmp < c.count(); tmp++) { heap[tmp] = c[tmp]; size = tmp + 1; for (uint j = 1; j < size; j++) { QString k = heap[j].lower(); QString r = heap[j]; for (i = j - 1; i >= 0; i--) { if ( QString::compare(k, heap[i].lower()) > 0 ) break; heap[i+1] = heap[i]; } heap[i+1] = r; } } // now, copy sorted data back to QStringList it = c.begin(); for (i = 0; i < (int)size; i++) *it++ = heap[i]; delete[] heap; } #ifdef Q_WS_PM static QString getBrowserSetting (PCSZ key) { static const PSZ app = "WPURLDEFAULTSETTINGS"; QString value; ULONG keyLen = 0; if ( PrfQueryProfileSize( HINI_USERPROFILE, app, key, &keyLen ) && keyLen ) { char *buf = new char [keyLen]; ULONG realLen = PrfQueryProfileString( HINI_USERPROFILE, app, key, NULL, buf, keyLen ); if (realLen) { realLen --; // excude zero terminator buf[realLen] = 0; value = QString::fromLocal8Bit(buf); } delete[] buf; } return value; } #endif void openURL(const QString &url) { //fprintf(stderr, "openURL: [%s]\n", url.latin1()); bool useCustom = TRUE; int colon = url.find(':'); if ( colon == -1 ) colon = 0; QString service = url.left( colon ); if ( service == "jabber" || service == "jid" ) { // TODO return; } #ifdef Q_WS_WIN if(option.browser == 0) useCustom = FALSE; #endif #ifdef Q_WS_PM if(option.browser == 0) useCustom = FALSE; #endif #ifdef Q_WS_X11 if(option.browser == 0 || option.browser == 2) useCustom = FALSE; #endif #ifdef Q_WS_MAC useCustom = FALSE; #endif if(useCustom) { bool isMail = FALSE; QString s = url; if(url.left(7).lower() == "mailto:") { #ifdef Q_WS_PM // mozilla/thunderbird doesn't understand e-mails w/o 'mailto:' #else s.remove(0, 7); #endif isMail = TRUE; } QStringList args; if(isMail) { if(option.customMailer.isEmpty()) { QMessageBox::critical(0, CAP(QObject::tr("URL error")), QObject::tr("Unable to open the URL. You have not selected a mailer (see Options).")); return; } args += QStringList::split(' ', option.customMailer); } else { if(option.customBrowser.isEmpty()) { QMessageBox::critical(0, CAP(QObject::tr("URL error")), QObject::tr("Unable to open the URL. You have not selected a browser (see Options).")); return; } args += QStringList::split(' ', option.customBrowser); } args += s; QProcess cmd(args); if(!cmd.start()) { QMessageBox::critical(0, CAP(QObject::tr("URL error")), QObject::tr("Unable to open the URL. Ensure that your custom browser/mailer exists (see Options).")); } } else { #ifdef Q_WS_WIN QCString cs = url.local8Bit(); if ((unsigned int)::ShellExecuteA(NULL,NULL,cs.data(),NULL,NULL,SW_SHOW) <= 32) { QMessageBox::critical(0, CAP(QObject::tr("URL error")), QObject::tr("Unable to open the URL. Ensure that you have a web browser installed.")); } #endif #ifdef Q_WS_PM QString exe, params, workDir; if (url.left(7).lower() == "mailto:") { exe = getBrowserSetting("DefaultMailExe"); params = getBrowserSetting("DefaultMailParameters"); workDir = getBrowserSetting("DefaultMailWorkingDir"); } else if (url.left(5).lower() == "news:") { exe = getBrowserSetting("DefaultNewsExe"); params = getBrowserSetting("DefaultNewsParameters"); workDir = getBrowserSetting("DefaultNewsWorkingDir"); } else if (url.left(4).lower() == "ftp:") { exe = getBrowserSetting("DefaultFTPExe"); params = getBrowserSetting("DefaultFTPParameters"); workDir = getBrowserSetting("DefaultFTPWorkingDir"); } else if (url.left(4).lower() == "irc:") { exe = getBrowserSetting("DefaultIRCExe"); params = getBrowserSetting("DefaultIRCParameters"); workDir = getBrowserSetting("DefaultIRCWorkingDir"); } // if the specific app is not found, fallback to the general broswer if (exe.isEmpty()) { exe = getBrowserSetting("DefaultBrowserExe"); params = getBrowserSetting("DefaultParameters"); workDir = getBrowserSetting("DefaultWorkingDir"); } bool success = false; if (!exe.isEmpty()) { QStringList args; args += exe; if (!params.isEmpty()) args += params; args += url; QProcess cmd(args); if (!workDir.isEmpty()) cmd.setWorkingDirectory(workDir); success = cmd.start(); } if (!success) QMessageBox::critical(0, CAP(QObject::tr("URL error")), QObject::tr("Unable to open the URL. Ensure that you have a web " "browser installed.")); #endif #ifdef Q_WS_X11 // KDE if(option.browser == 0) { QStringList args; args += "kfmclient"; args += "exec"; args += url; QProcess cmd(args); if(!cmd.start()) { QMessageBox::critical(0, CAP(QObject::tr("URL error")), QObject::tr("Unable to open the URL. Ensure that you have KDE installed.")); } } // GNOME 2 else if(option.browser == 2) { QStringList args; args += "gnome-open"; args += url; QProcess cmd(args); if(!cmd.start()) { QMessageBox::critical(0, CAP(QObject::tr("URL error")), QObject::tr("Unable to open the URL. Ensure that you have GNOME 2 installed.")); } } #endif #ifdef Q_WS_MAC // Use Internet Config to hand the URL to the appropriate application, as // set by the user in the Internet Preferences pane. // NOTE: ICStart could be called once at Psi startup, saving the // ICInstance in a global variable, as a minor optimization. // ICStop should then be called at Psi shutdown if ICStart succeeded. ICInstance icInstance; OSType psiSignature = 'psi '; OSStatus error = ::ICStart( &icInstance, psiSignature ); if ( error == noErr ) { ConstStr255Param hint( 0x0 ); QCString cs = url.local8Bit(); const char* data = cs.data(); long length = cs.length(); long start( 0 ); long end( length ); // Don't bother testing return value (error); launched application will report problems. ::ICLaunchURL( icInstance, hint, data, length, &start, &end ); ICStop( icInstance ); } #endif } } static bool sysinfo_done = FALSE; static int timezone_offset = 0; static QString timezone_str = "N/A"; static QString os_str = "Unknown"; #if defined(Q_WS_X11) || defined(Q_WS_MAC) #include #include #include #include #endif static void getSysInfo() { #if defined(Q_WS_X11) || defined(Q_WS_MAC) || defined(Q_WS_PM) time_t x; time(&x); char str[256]; char fmt[32]; strcpy(fmt, "%z"); strftime(str, 256, fmt, localtime(&x)); if(strcmp(fmt, str)) { QString s = str; if(s.at(0) == '+') s.remove(0,1); s.truncate(s.length()-2); timezone_offset = s.toInt(); } strcpy(fmt, "%Z"); strftime(str, 256, fmt, localtime(&x)); if(strcmp(fmt, str)) timezone_str = str; #endif #if defined(Q_WS_X11) struct utsname u; uname(&u); os_str.sprintf("%s", u.sysname); // get description about os enum LinuxName { LinuxNone = 0, LinuxMandrake, LinuxDebian, LinuxRedHat, LinuxGentoo, LinuxSlackware, LinuxSuSE, LinuxConectiva, LinuxCaldera, LinuxLFS, LinuxASP, // Russian Linux distros LinuxALT, LinuxPLD, // Polish Linux distros LinuxAurox, LinuxArch }; enum OsFlags { OsUseName = 0, OsUseFile, OsAppendFile }; struct OsInfo { LinuxName id; OsFlags flags; QString file; QString name; } osInfo[] = { { LinuxMandrake, OsUseFile, "/etc/mandrake-release", "Mandrake Linux" }, { LinuxDebian, OsAppendFile, "/etc/debian_version", "Debian GNU/Linux" }, { LinuxGentoo, OsUseFile, "/etc/gentoo-release", "Gentoo Linux" }, { LinuxSlackware, OsAppendFile, "/etc/slackware-version", "Slackware Linux" }, { LinuxPLD, OsUseFile, "/etc/pld-release", "PLD Linux" }, { LinuxAurox, OsUseName, "/etc/aurox-release", "Aurox Linux" }, { LinuxArch, OsUseFile, "/etc/arch-release", "Arch Linux" }, { LinuxLFS, OsAppendFile, "/etc/lfs-release", "LFS Linux" }, // untested { LinuxSuSE, OsUseFile, "/etc/SuSE-release", "SuSE Linux" }, { LinuxConectiva, OsUseFile, "/etc/conectiva-release", "Conectiva Linux" }, { LinuxCaldera, OsUseFile, "/etc/.installed", "Caldera Linux" }, // many distros use the /etc/redhat-release for compatibility, so RedHat will be the last :) { LinuxRedHat, OsUseFile, "/etc/redhat-release", "RedHat Linux" }, { LinuxNone, OsUseName, "", "" } }; for (int i = 0; osInfo[i].id != LinuxNone; i++) { QFileInfo fi( osInfo[i].file ); if ( fi.exists() ) { QString desc; QFile f( osInfo[i].file ); f.open( IO_ReadOnly ); f.readLine( desc, 128 ); desc = desc.stripWhiteSpace (); switch ( osInfo[i].flags ) { case OsUseFile: os_str = desc; break; case OsUseName: os_str = osInfo[i].name; break; case OsAppendFile: os_str = osInfo[i].name + " (" + desc + ")"; break; } break; } } #elif defined(Q_WS_PM) os_str = "OS/2"; #elif defined(Q_WS_MAC) os_str = "Mac OS X"; #endif #if defined(Q_WS_WIN) TIME_ZONE_INFORMATION i; //GetTimeZoneInformation(&i); //timezone_offset = (-i.Bias) / 60; memset(&i, 0, sizeof(i)); bool inDST = (GetTimeZoneInformation(&i) == TIME_ZONE_ID_DAYLIGHT); int bias = i.Bias; if(inDST) bias += i.DaylightBias; timezone_offset = (-bias) / 60; timezone_str = ""; for(int n = 0; n < 32; ++n) { uint w = inDST ? i.DaylightName[n] : i.StandardName[n]; if(w == 0) break; timezone_str += QChar(w); } Qt::WindowsVersion v = QApplication::winVersion(); if(v == Qt::WV_95) os_str = "Windows 95"; else if(v == Qt::WV_98) os_str = "Windows 98"; else if(v == Qt::WV_Me) os_str = "Windows Me"; else if(v == Qt::WV_DOS_based) os_str = "Windows 9x/Me"; else if(v == Qt::WV_NT) os_str = "Windows NT 4.x"; else if(v == Qt::WV_2000) os_str = "Windows 2000"; else if(v == Qt::WV_XP) os_str = "Windows XP"; #if QT_VERSION >= 0x030300 else if(v == Qt::WV_2003) os_str = "Windows Server 2003"; #endif else if(v == Qt::WV_NT_based) os_str = "Windows NT"; #endif sysinfo_done = TRUE; } QString getOSName() { if(!sysinfo_done) getSysInfo(); return os_str; } int getTZOffset() { if(!sysinfo_done) getSysInfo(); return timezone_offset; } QString getTZString() { if(!sysinfo_done) getSysInfo(); return timezone_str; } #ifdef Q_WS_X11 QString getResourcesDir() { return PSI_DATADIR; } QString getHomeDir() { QDir proghome(QDir::homeDirPath() + "/.psi"); if(!proghome.exists()) { QDir home = QDir::home(); home.mkdir(".psi"); chmod(QFile::encodeName(proghome.path()), 0700); } return proghome.path(); } #endif #ifdef Q_WS_WIN QString getResourcesDir() { #if QT_VERSION >= 0x030200 return qApp->applicationDirPath(); #else char baseName[MAX_PATH]; GetModuleFileNameA(GetModuleHandle(NULL), baseName, MAX_PATH); QString base(baseName); int idx = base.findRev('\\'); if (-1 == idx) return "."; base.truncate(idx); QDir baseDir(base); return baseDir.absPath(); #endif } QString getHomeDir() { QString base; // Windows 9x if(QDir::homeDirPath() == QDir::rootDirPath()) base = "."; // Windows NT/2K/XP variant else base = QDir::homeDirPath(); // no trailing slash if(base.at(base.length()-1) == '/') base.truncate(base.length()-1); QDir proghome(base + "/PsiData"); if(!proghome.exists()) { QDir home(base); home.mkdir("PsiData"); } return proghome.path(); } #endif #ifdef Q_WS_PM QString getResourcesDir() { #if QT_VERSION >= 0x030200 return qApp->applicationDirPath(); #else # error Not implemented! #endif } QString getHomeDir() { QString base = QDir::homeDirPath(); // no trailing slash if(base.at(base.length()-1) == '/') base.truncate(base.length()-1); QDir proghome(base + "/PsiData"); if(!proghome.exists()) { QDir home(base); home.mkdir("PsiData"); } return proghome.path(); } #endif #ifdef Q_WS_MAC /******************************************************************************/ /* Get path to Resources directory as a string. */ /* Return an empty string if can't find it. */ /******************************************************************************/ QString getResourcesDir() { // System routine locates resource files. We "know" that Psi.icns is // in the Resources directory. QString resourcePath; CFBundleRef mainBundle = CFBundleGetMainBundle(); CFStringRef resourceCFStringRef = CFStringCreateWithCString( NULL, "application.icns", kCFStringEncodingASCII ); CFURLRef resourceURLRef = CFBundleCopyResourceURL( mainBundle, resourceCFStringRef, NULL, NULL ); if ( resourceURLRef ) { CFStringRef resourcePathStringRef = CFURLCopyFileSystemPath( resourceURLRef, kCFURLPOSIXPathStyle ); const char* resourcePathCString = CFStringGetCStringPtr( resourcePathStringRef, kCFStringEncodingASCII ); if ( resourcePathCString ) { resourcePath.setLatin1( resourcePathCString ); } else { // CFStringGetCStringPtr failed; use fallback conversion CFIndex bufferLength = CFStringGetLength( resourcePathStringRef ) + 1; char* resourcePathCString = new char[ bufferLength ]; Boolean conversionSuccess = CFStringGetCString( resourcePathStringRef, resourcePathCString, bufferLength, kCFStringEncodingASCII ); if ( conversionSuccess ) { resourcePath = resourcePathCString; } delete [] resourcePathCString; // I own this } CFRelease( resourcePathStringRef ); // I own this } // Remove the tail component of the path if ( ! resourcePath.isNull() ) { QFileInfo fileInfo( resourcePath ); resourcePath = fileInfo.dirPath( true ); } return resourcePath; } QString getHomeDir() { QDir proghome(QDir::homeDirPath() + "/.psi"); if(!proghome.exists()) { QDir home = QDir::home(); home.mkdir(".psi"); chmod(QFile::encodeName(proghome.path()), 0700); } return proghome.path(); } #endif QString getHistoryDir() { QDir history(pathToProfile(activeProfile) + "/history"); if (!history.exists()) { QDir profile(pathToProfile(activeProfile)); profile.mkdir("history"); } return history.path(); } QString getVCardDir() { QDir vcard(pathToProfile(activeProfile) + "/vcard"); if (!vcard.exists()) { QDir profile(pathToProfile(activeProfile)); profile.mkdir("vcard"); } return vcard.path(); } bool fileCopy(const QString &src, const QString &dest) { QFile in(src); QFile out(dest); if(!in.open(IO_ReadOnly)) return FALSE; if(!out.open(IO_WriteOnly)) return FALSE; char *dat = new char[16384]; int n = 0; while(!in.atEnd()) { n = in.readBlock(dat, 16384); if(n == -1) { delete dat; return FALSE; } out.writeBlock(dat, n); } delete dat; out.close(); in.close(); return TRUE; } class BeepThread : public QThread { public: BeepThread() : sem(1), terminate(false) {} ~BeepThread() { while (sem.available() != sem.total()) sem--; } void run() { lock.lock(); while (!terminate) { while (!terminate && queue.count()) { Beep beep = queue.front(); queue.pop_front(); lock.unlock(); #if defined(Q_WS_WIN) if (beep.freq && beep.dur) ::Beep(beep.freq, beep.dur); else #elif defined(Q_WS_PM) if (beep.freq && beep.dur) DosBeep(beep.freq, beep.dur); else #endif QApplication::beep(); lock.lock(); } lock.unlock(); sem++; lock.lock(); } lock.unlock(); } void beginSequence() { lock.lock(); queue.clear(); // stop the previous sequence } void addNote(ulong freq, ulong dur) { queue.push_back(Beep(freq, dur)); } void endSequence() { lock.unlock(); if (!sem.available()) sem--; } void stop() { lock.lock(); terminate = true; lock.unlock(); if (!sem.available()) sem--; wait(); } private: struct Beep { Beep() : freq(0), dur(0) {} Beep(ulong f, ulong d) : freq(f), dur(d) {} const ulong freq; const ulong dur; }; QMutex lock; QSemaphore sem; // not QWaitCond cause we need level-triggered semantics QValueList queue; bool terminate; }; static BeepThread *beepThread = 0; static void cleanupBeepThread() { if (beepThread) { beepThread->stop(); delete beepThread; beepThread = 0; } } void soundPlay(const QString &str) { if (str.startsWith("!beep")) { QStringList seq = QStringList::split(':',str); if (seq[0].length() == 5 /* strlen("!beep") */) { if (beepThread == 0) { beepThread = new BeepThread(); if (beepThread) { qAddPostRoutine(cleanupBeepThread); beepThread->start(); } } Q_ASSERT(beepThread); if (beepThread) { beepThread->beginSequence(); #if defined(Q_WS_WIN) || defined(Q_WS_PM) ulong lastDur = 100; size_t i = 1; for (; i < seq.count(); ++i) { QStringList note = QStringList::split(',',seq[i]); if (note.count() < 1) break; ulong freq = note[0].toULong(); if (freq < 0x25 || freq > 0x7FFF) break; ulong dur = lastDur; if (note.count() > 2) break; if (note.count() == 2) { dur = note[1].toULong(); // 3s is the reasonable maximum for the duration if (dur == 0 || dur > 3000) break; lastDur = dur; } beepThread->addNote(freq, dur); } if (i == 1) // didn't find a single note #endif beepThread->addNote(0, 0); beepThread->endSequence(); } return; } } if(!QFile::exists(str)) return; #if defined(Q_WS_WIN) || defined(Q_WS_PM) || defined(Q_WS_MAC) QSound::play(str); #else if(!option.player.isEmpty()) { QStringList args; args = QStringList::split(' ', option.player); args += str; QProcess cmd(args); if(!cmd.start()) wait3(NULL,WNOHANG,NULL); } #endif } XMPP::Status makeStatus(int x, const QString &str) { XMPP::Status s; s.setStatus(str); if(x == STATUS_OFFLINE) s.setIsAvailable(false); else if(x == STATUS_INVISIBLE) s.setIsInvisible(true); else { if(x == STATUS_AWAY) s.setShow("away"); else if(x == STATUS_XA) s.setShow("xa"); else if(x == STATUS_DND) s.setShow("dnd"); else if(x == STATUS_CHAT) s.setShow("chat"); } return s; } int makeSTATUS(const XMPP::Status &s) { int type = STATUS_ONLINE; if(!s.isAvailable()) type = STATUS_OFFLINE; else if(s.isInvisible()) type= STATUS_INVISIBLE; else { if(s.show() == "away") type = STATUS_AWAY; else if(s.show() == "xa") type = STATUS_XA; else if(s.show() == "dnd") type = STATUS_DND; else if(s.show() == "chat") type = STATUS_CHAT; } return type; } #include QLayout *rw_recurseFindLayout(QLayout *lo, QWidget *w) { //printf("scanning layout: %p\n", lo); QLayoutIterator it = lo->iterator(); for(QLayoutItem *i; (i = it.current()); ++it) { //printf("found: %p,%p\n", i->layout(), i->widget()); QLayout *slo = i->layout(); if(slo) { QLayout *tlo = rw_recurseFindLayout(slo, w); if(tlo) return tlo; } else if(i->widget() == w) return lo; } return 0; } QLayout *rw_findLayoutOf(QWidget *w) { return rw_recurseFindLayout(w->parentWidget()->layout(), w); } void replaceWidget(QWidget *a, QWidget *b) { if(!a) return; QLayout *lo = rw_findLayoutOf(a); if(!lo) return; //printf("decided on this: %p\n", lo); if(lo->inherits("QBoxLayout")) { QBoxLayout *bo = (QBoxLayout *)lo; int n = bo->findWidget(a); bo->insertWidget(n+1, b); delete a; } } void closeDialogs(QWidget *w) { // close qmessagebox? const QObjectList *list = w->children(); if(list) { QObjectListIt it(*list); QPtrList dialogs; for(QObject *o; (o = it.current()); ++it) { if(o->inherits("QDialog")) dialogs.append((QDialog *)o); } QPtrListIterator dit(dialogs); for(QDialog *w; (w = dit.current()); ++dit) w->close(); } } QString enc822jid(const QString &s) { QString out; for(int n = 0; n < (int)s.length(); ++n) { if(s[n] == '\\' || s[n] == '<' || s[n] == '>') { QString hex; hex.sprintf("\\x%02X", (unsigned char )s[n]); out.append(hex); } else out += s[n]; } return out; } QString dec822jid(const QString &s) { QString out; for(int n = 0; n < (int)s.length(); ++n) { if(s[n] == '\\' && n + 3 < (int)s.length()) { int x = n + 1; n += 3; if(s[x] != 'x') continue; ushort val = 0; val += hexChar2int(s[x+1])*16; val += hexChar2int(s[x+2]); QChar c(val); out += c; } else out += s[n]; } return out; } #ifdef Q_WS_X11 #include #include // needed for WM_CLASS hinting void x11wmClass(Display *dsp, WId wid, QString resName) { char app_name[] = "psi"; //Display *dsp = x11Display(); // get the display //WId win = winId(); // get the window XClassHint classhint; // class hints classhint.res_name = (char *)resName.latin1(); // res_name classhint.res_class = app_name; // res_class XSetClassHint(dsp, wid, &classhint); // set the class hints } //>>>-- Nathaniel Gray -- Caltech Computer Science ------> //>>>-- Mojave Project -- http://mojave.cs.caltech.edu --> // Copied from http://www.nedit.org/archives/discuss/2002-Aug/0386.html // Helper function bool getCardinal32Prop(Display *display, Window win, char *propName, long *value) { Atom nameAtom, typeAtom, actual_type_return; int actual_format_return, result; unsigned long nitems_return, bytes_after_return; long *result_array=NULL; nameAtom = XInternAtom(display, propName, False); typeAtom = XInternAtom(display, "CARDINAL", False); if (nameAtom == None || typeAtom == None) { //qDebug("Atoms not interned!"); return false; } // Try to get the property result = XGetWindowProperty(display, win, nameAtom, 0, 1, False, typeAtom, &actual_type_return, &actual_format_return, &nitems_return, &bytes_after_return, (unsigned char **)&result_array); if( result != Success ) { //qDebug("not Success"); return false; } if( actual_type_return == None || actual_format_return == 0 ) { //qDebug("Prop not found"); return false; } if( actual_type_return != typeAtom ) { //qDebug("Wrong type atom"); } *value = result_array[0]; XFree(result_array); return true; } // Get the desktop number that a window is on bool desktopOfWindow(Window *window, long *desktop) { Display *display = qt_xdisplay(); bool result = getCardinal32Prop(display, *window, (char *)"_NET_WM_DESKTOP", desktop); //if( result ) // qDebug("Desktop: " + QString::number(*desktop)); return result; } // Get the current desktop the WM is displaying bool currentDesktop(long *desktop) { Window rootWin; Display *display = qt_xdisplay();; bool result; rootWin = RootWindow(qt_xdisplay(), XDefaultScreen(qt_xdisplay())); result = getCardinal32Prop(display, rootWin, (char *)"_NET_CURRENT_DESKTOP", desktop); //if( result ) // qDebug("Current Desktop: " + QString::number(*desktop)); return result; } #endif void bringToFront(QWidget *w, bool grabFocus /* = true */) { #if defined(Q_WS_X11) // If we're not on the current desktop, do the hide/show trick long dsk, curr_dsk; Window win = w->winId(); if(desktopOfWindow(&win, &dsk) && currentDesktop(&curr_dsk)) { if(dsk != curr_dsk) { w->hide(); //qApp->processEvents(); } } #else // FIXME: multi-desktop hacks for Win and Mac required #endif w->show(); if(w->isMinimized()) { //w->hide(); if(w->isMaximized()) w->showMaximized(); else w->showNormal(); } #ifdef Q_WS_WIN w->raise(); if (grabFocus) { w->setActiveWindow(); } else { if (GetTopWindow(NULL) != w->winId()) { // On Win32, raise() cannot move a window on top of the currently // active window w/o making the former active. To overcome this, // we use a trick by first making the window floating on top and // then restoring the normal behavior. SetWindowPos(w->winId(), HWND_TOPMOST, 0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE); SetWindowPos(w->winId(), HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE); } } #else if(grabFocus) w->setActiveWindow(); else w->raise(); #endif #if defined(Q_WS_PM) // When the widget is totally outside the screen bounds after making it shown, // we assume it's on a different (invisible) XPager's page. The assumption is // not so safe, but is there any other way to determine XPager is in action?.. QRect geo = w->frameGeometry(); QRect scrGeo = qApp->desktop()->screenGeometry(); if (!w->isMinimized() && !scrGeo.contains(geo)) { // We assume that XPager stores invisible pages in the coordinate space // beyond (0,0)x(screen_width,screen_height) wiht a 8 px gap between // pages. Move it to the visible page. int sw = scrGeo.width() + 8; int sh = scrGeo.height() + 8; int x = geo.x() % sw; if (x < 0) x = sw + x; int y = geo.y() % sh; if (y < 0) y = sh + y; w->move (x, y); } #endif } bool operator!=(const QMap &m1, const QMap &m2) { if ( m1.size() != m2.size() ) return true; QMap::ConstIterator it = m1.begin(), it2; for ( ; it != m1.end(); ++it) { it2 = m2.find( it.key() ); if ( it2 == m2.end() ) return true; if ( it.data() != it2.data() ) return true; } return false; } #if defined(Q_OS_OS2) && !defined(QT_OS2_NO_SYSEXCEPTIONS) int psiOS2SysXcptCallback( QtOS2SysXcptReq req, QtOS2SysXcptWriter writer, int /* reserved */ ) { switch( req ) { case QtOS2SysXcptReq_AppName: if ( writer ) writer( PROG_NAME.latin1() ); return TRUE; case QtOS2SysXcptReq_AppVer: if ( writer ) writer( PROG_VERSION.latin1() ); return TRUE; case QtOS2SysXcptReq_ReportTo: if ( writer ) writer( "qt-traps@hugaida.com" ); return TRUE; default: break; } return FALSE; } #endif