/** Copyright (c) 2020-2022 The Creators of Simphone

    class HotKeyThread (QThread): read X11 events to catch global key presses
    class SimCore (QObject): handle simcore events, read contact list and other API-related things
    class SimThread (QThread): call sim_exit_user_(blocking function)
    class SimEventBase (QEvent): parent of all event classes defined here
    class SimParam: read and write values of simcore parameters

    See the file COPYING.LESSER.txt for copying permission.
**/

#include "../simcore/config.h"

#include "qtfix.h"
#include "sounds.h"
#include "contacts.h"
#include "chatframe.h"
#include "settings.h"
#include "transfers.h"

#include "login.h"
#include "seedgen.h"
#include "keygen.h"
#include "generate.h"
#include "showseed.h"

#include <QApplication>
#include <QAbstractEventDispatcher>
#include <QTextStream>
#include <QDialogButtonBox>
#include <QLabel>

#include <time.h>

#ifndef _WIN32
#include <unistd.h>
#include <signal.h>
#include <errno.h>
#else
#include <windows.h>
#endif

#ifdef __APPLE__
#include <Carbon/Carbon.h>
#endif

#ifdef HAVE_LIBXCB
#include <xcb/xcb.h>
#include <xcb/xcb_keysyms.h>
#endif

static bool g_xferErrorReceived = false;
static bool g_socksErrorReceived = false;
static bool g_DHTinit = true;
static const char * g_tickEvent;
static int g_DHTstatus = -1;
QByteArray g_exeName;

#ifdef _DEBUG
int g_terminate_signal = -1;
#else
int g_terminate_signal = 0;
#endif

QByteArray SimParam::getString(const char * param)
{
  QByteArray value;
  simtype data = sim_param_get_(param);
  int type = sim_get_type(data);
  if (type == SIMSTRING || type == SIMPOINTER) value = QByteArray(sim_get_pointer(data), sim_get_length(data));
  sim_param_free_(data);
  return value;
}

bool SimParam::getNumber(const char * param, int * value)
{
  simtype data = sim_param_get_(param);
  *value = int(sim_get_number(data));
  bool ok = sim_get_type(data) == SIMNUMBER;
  sim_number_free(data);
  return ok;
}

int SimParam::get(const char * param)
{
  simtype data = sim_param_get_(param);
  int value = int(sim_get_number(data));
  sim_number_free(data);
  return value;
}

int SimParam::getDefault(const char * param, int defaultValue, int * max, int * min)
{
  simtype data = sim_number_new(defaultValue);
  sim_param_get_limits_(param, &data, min, max, 0);
  int value = int(sim_get_number(data));
  sim_number_free(data);
  return value;
}

enum {
  simev_fatal,
  simev_contact,
  simev_status,
  simev_message,
  simev_edit,
  simev_msgsent,
  simev_notsent,
  simev_xfer,
  simev_audio,
  simev_speech,
  simev_keygen,
  simev_history,
  simev_net,
  simev_error,
  simev_log,
  simev_tick,
  simev_numberOfEvents
};

class SimEventBase : public QEvent
{
public:
  enum E_EventType {
    event_none = QEvent::User,
    event_contact, // SIM_EVENT_CONTACT or contact list has changed
    event_status,  // SIM_EVENT_STATUS (CONTACT_KEY_STATUS)
    event_flags,   // SIM_EVENT_STATUS (CONTACT_KEY_FLAGS)
    event_info,    // SIM_EVENT_STATUS (CONTACT_KEY_NICK, CONTACT_KEY_LINE or CONTACT_KEY_EDIT)
    event_msg,     // SIM_EVENT_MSG or SIM_EVENT_HISTORY
    event_edit,    // SIM_EVENT_EDIT
    event_sent,    // SIM_EVENT_SENT or SIM_EVENT_NOTSENT
    event_xfer,    // SIM_EVENT_XFER (handle)
    event_file,    // SIM_EVENT_XFER (SIM_XFER_GET_RECEIVED or SIM_XFER_GET_SENT) or SIM_EVENT_HISTORY (FILE)
    event_audio,   // SIM_EVENT_AUDIO or SIM_EVENT_HISTORY (CALL)
    event_speech,  // SIM_EVENT_SPEECH
    event_keygen,  // SIM_EVENT_KEYGEN
    event_net,     // SIM_EVENT_NET
    event_device,  // SIM_EVENT_NET (SIM_EVENT_NET_DEVICE)
    event_error,   // SIM_EVENT_ERROR or SIM_EVENT_HISTORY (STATUS OFF)
    event_log      // SIM_EVENT_LOG
  };

  SimEventBase(E_EventType type, simnumber id)
    : QEvent(QEvent::Type(type)), m_simId(id) {}

  simnumber m_simId; // id from simcore
};

static QList<SimEventBase *> g_fileErrorsReceived;

class ContactChangedEvent : public SimEventBase
{
public:
  ContactChangedEvent(simnumber id)
    : SimEventBase(event_contact, id) {}
};

class ContactStatusChangedEvent : public SimEventBase
{
public:
  ContactStatusChangedEvent(simnumber id, int oldStatus, int newStatus)
    : SimEventBase(event_status, id), m_oldStatus(oldStatus), m_newStatus(newStatus) {}

  int m_oldStatus, m_newStatus;
};

class ContactFlagsChangedEvent : public SimEventBase
{
public:
  ContactFlagsChangedEvent(simnumber id, int flags)
    : SimEventBase(event_flags, id), m_flags(flags) {}

  int m_flags;
};

class ContactInfoChangedEvent : public SimEventBase
{
public:
  ContactInfoChangedEvent(simnumber id)
    : SimEventBase(event_info, id) {}
};

class MessageReceivedEvent : public SimEventBase
{
public:
  MessageReceivedEvent(simnumber id, int ndx, bool noNotify)
    : SimEventBase(event_msg, id), m_ndx(ndx), m_noNotify(noNotify) {}

  int m_ndx;
  bool m_noNotify;
};

class MessageEditEvent : public SimEventBase
{
public:
  MessageEditEvent(simnumber id, int ndx)
    : SimEventBase(event_edit, id), m_ndx(ndx) {}

  int m_ndx;
};

class MessageSentEvent : public SimEventBase
{
public:
  MessageSentEvent(simnumber id, const std::vector<int> * ndxs, char reason)
    : SimEventBase(event_sent, id), m_ndxs(ndxs), m_reason(reason) {}

  const std::vector<int> * m_ndxs;
  char m_reason;
};

class TransferStateEvent : public SimEventBase
{
public:
  TransferStateEvent(simnumber id, simnumber xferId, const char * type)
    : SimEventBase(event_xfer, id), m_xferId(xferId), m_type(type) {}

  simnumber m_xferId;
  const char * m_type;
};

class TransferEvent : public SimEventBase
{
public:
  TransferEvent(simnumber id, simnumber xferId, const QString & oldName, const QString & newName)
    : SimEventBase(event_file, id), m_oldName(oldName), m_newName(newName), m_xferId(xferId) {}

  QString m_oldName;
  QString m_newName;
  simnumber m_xferId;
};

class AudioEvent : public SimEventBase
{
public:
  AudioEvent(simnumber id, SimAudioState oldAudioState, SimAudioState newAudioState)
    : SimEventBase(event_audio, id), m_oldAudioState(oldAudioState), m_newAudioState(newAudioState) {}

  SimAudioState m_oldAudioState, m_newAudioState;
};

class SpeechEvent : public SimEventBase
{
public:
  SpeechEvent(simnumber id, int err, int prob, int percent)
    : SimEventBase(event_speech, id), m_err(err), m_prob(prob), m_percent(percent) {}

  int m_err, m_prob, m_percent;
};

class KeygenEvent : public SimEventBase
{
public:
  KeygenEvent(simnumber id, int err, simnumber time2gen, const char * address)
    : SimEventBase(event_keygen, id), m_err(err), m_time2gen(time2gen), m_address(address) {}

  int m_err;
  simnumber m_time2gen;
  QString m_address;
};

class NetEvent : public SimEventBase
{
public:
  NetEvent(simnumber id, char code, int err, const QString & name)
    : SimEventBase(event_net, id), m_code(code), m_err(err), m_name(name) {}

  char m_code;
  int m_err;
  QString m_name;
};

class InvalidDeviceEvent : public SimEventBase
{
public:
  InvalidDeviceEvent(const char * output, const char * input, const char * ring)
    : SimEventBase(event_device, 0), m_output(output), m_input(input), m_ring(ring) {}

  QString m_output, m_input, m_ring;
};

class ErrorEvent : public SimEventBase
{
public:
  ErrorEvent(simnumber id, char code, int err, const QString & name)
    : SimEventBase(event_error, id), m_code(code), m_err(err), m_name(name) {}

  char m_code;
  int m_err;
  QString m_name;
};

class LogEvent : public SimEventBase
{
public:
  LogEvent(int level)
    : SimEventBase(event_log, 0), m_level(level) {}

  int m_level;
};

static simevent * g_oldSIMeventHandlers[simev_numberOfEvents];

#if defined(_WIN32) && !defined(_DEBUG)
template <typename T>
static void convertArgument(const T * src, wchar_t * dst)
{
  for (*dst++ = '"'; *src; *dst++ = *src++) {
    if (*src == '"' || *src == '\\') *dst++ = '\\';
  }
  *dst++ = '"';
  *dst = 0;
}
#endif

// Called when simphone has crashed
extern "C" void handleSIMeventFATAL(simnumber id, const simtype fatal)
{
  static int g_fatal_recursed = 0;
  int recursed = g_fatal_recursed;
  g_fatal_recursed = 1;
  if (g_oldSIMeventHandlers[simev_fatal]) g_oldSIMeventHandlers[simev_fatal](id, fatal);

  simtype err = recursed ? sim_nil() : sim_table_get(fatal, SIM_EVENT_FATAL);
  simnumber simres = sim_get_type(err) == SIMNUMBER ? sim_get_number(err) : SIM_OK;
  simtype text = sim_table_get(fatal, SIM_CMD_MSG_TEXT);
  const char * str = sim_get_type(text) == SIMPOINTER || sim_get_type(text) == SIMSTRING ? sim_get_pointer(text) : "";

  if (SimCore::isHidden() != 'h') {
#ifdef _WIN32
    BOOL ok = FALSE;
#ifndef _DEBUG
    const char * lang = qtfix::getLanguage();
    STARTUPINFOW si;
    PROCESS_INFORMATION pi;
    static wchar_t g_crashExe[MAX_PATH + 1], g_crashText[MAX_PATH + 33280];

    if ((simres == SIM_OK || simres == SIM_NO_ERROR) && GetModuleFileNameW(0, g_crashExe, MAX_PATH)) {
      convertArgument<wchar_t>(g_crashExe, g_crashText);
      wcscat(g_crashText, L" -nouser");
      if (*lang) convertArgument<char>(lang, g_crashText + wcslen(wcscat(g_crashText, L" -language ")));
      convertArgument<char>(str, g_crashText + wcslen(wcscat(g_crashText, L" -crash ")));
      GetStartupInfoW(&si);
      ok = CreateProcessW(g_crashExe, g_crashText, 0, 0, FALSE, 0, 0, 0, &si, &pi);
    }
#endif
    if (!ok) {
      char * dst = str ? strchr(str, '"') : 0;

      if (dst) *dst = 0;
      if (simres == SIM_OK || simres == SIM_NO_ERROR) {
        MessageBoxA(0, "Simphone has crashed. Please, do report this incident to the Creators of Simphone.\n\n"
                       "This application will now exit. We are sorry for the inconvenience.",
                    str, MB_OK | MB_APPLMODAL);
      } else {
        MessageBoxA(0, "Simphone has been stopped by Windows, because the EXE file is no longer accessible.\n\n"
                       "This application will now exit. We are sorry for the inconvenience.",
                    str, MB_OK | MB_APPLMODAL);
      }
    }
#else
    const char * lang = qtfix::getLanguage();
    if (simres != SIM_NO_ERROR || fork() <= 0) {
      errno = 0;
      if (!g_exeName.isEmpty()) {
        const char * style = qtfix::getStyleArgument();
        if (style) {
          execl(g_exeName.data(), g_exeName.data(), "-nouser", "-crash", str, "-style", style, "-language", lang, NULL);
        } else {
          execl(g_exeName.data(), g_exeName.data(), "-nouser", "-crash", str, "-language", lang, NULL);
        }
      }
      _exit(EXIT_NO_ERROR - errno);
    }
#endif // _WIN32
  }
  if (simres != SIM_NO_ERROR) _exit(EXIT_CRASH_FAILED);
}

extern "C" void handleSIMeventCONTACT(simnumber id, const simtype contact)
{
  if (g_oldSIMeventHandlers[simev_contact]) g_oldSIMeventHandlers[simev_contact](id, contact);

  simnumber err = sim_table_get_number(contact, SIM_EVENT_CONTACT);

  QCoreApplication::postEvent(SimCore::get(), new ContactChangedEvent(err == SIM_OK ? id : 0));
}

extern "C" void handleSIMeventSTATUS(simnumber id, const simtype status)
{
  if (g_oldSIMeventHandlers[simev_status]) g_oldSIMeventHandlers[simev_status](id, status);

  simtype newSt = sim_table_get(status, CONTACT_KEY_STATUS);
  simtype oldSt = sim_table_get(status, SIM_EVENT_STATUS);
  simtype flags = sim_table_get(status, CONTACT_KEY_FLAGS);

  if (sim_get_type(newSt) == SIMNUMBER && sim_get_type(oldSt) == SIMNUMBER) {
    SimEventBase * ev = new ContactStatusChangedEvent(id, int(sim_get_number(oldSt)), int(sim_get_number(newSt)));
    QCoreApplication::postEvent(SimCore::get(), ev);
  }

  if (sim_get_type(flags) == SIMNUMBER) {
    if (sim_get_type(oldSt) != SIMNUMBER) {
      if (!(sim_get_number(flags) & (CONTACT_FLAG_UTF | CONTACT_FLAG_XFER | CONTACT_FLAG_AUDIO))) {
        QCoreApplication::postEvent(SimCore::get(), new ErrorEvent(id, 'p', SIM_MSG_OVERFLOW, 0));
      }
    }
    QCoreApplication::postEvent(SimCore::get(), new ContactFlagsChangedEvent(id, int(sim_get_number(flags))));
  }

  if (!sim_table_get_pointer(status, CONTACT_KEY_NICK) && !sim_table_get_pointer(status, CONTACT_KEY_LINE)) {
    if (sim_get_type(sim_table_get(status, CONTACT_KEY_EDIT)) != SIMNUMBER) return;
  }
  QCoreApplication::postEvent(SimCore::get(), new ContactInfoChangedEvent(id));
}

extern "C" void handleSIMeventMSG(simnumber id, const simtype msg)
{
  if (g_oldSIMeventHandlers[simev_message]) g_oldSIMeventHandlers[simev_message](id, msg);

  int msgNdx = int(sim_table_get_number(msg, SIM_EVENT_MSG));

  QCoreApplication::postEvent(SimCore::get(), new MessageReceivedEvent(id, msgNdx, false));
}

extern "C" void handleSIMeventEDIT(simnumber id, const simtype msg)
{
  if (g_oldSIMeventHandlers[simev_edit]) g_oldSIMeventHandlers[simev_edit](id, msg);

  int msgNdx = int(sim_table_get_number(msg, SIM_EVENT_EDIT));

  QCoreApplication::postEvent(SimCore::get(), new MessageEditEvent(id, msgNdx));
}

extern "C" void handleSIMeventSENT(simnumber id, const simtype event)
{
  if (g_oldSIMeventHandlers[simev_msgsent]) g_oldSIMeventHandlers[simev_msgsent](id, event);

  simnumber sent = sim_table_get_number(event, SIM_EVENT_SENT);
  simtype indexes = sim_table_get_array_number(event, SIM_EVENT_MSG);
  std::vector<int> * ndxs = new std::vector<int>();
  ndxs->reserve(sim_get_length(indexes));
  for (unsigned i = 1; i <= sim_get_length(indexes); ++i) ndxs->push_back(int(sim_array_get_number(indexes, i)));

  QCoreApplication::postEvent(SimCore::get(), new MessageSentEvent(id, ndxs, sent ? 'r' : 's'));
}

extern "C" void handleSIMeventNOTSENT(simnumber id, const simtype event)
{
  if (g_oldSIMeventHandlers[simev_notsent]) g_oldSIMeventHandlers[simev_notsent](id, event);

  simnumber notsent = sim_table_get_number(event, SIM_EVENT_NOTSENT);
  simtype indexes = sim_table_get_array_number(event, SIM_EVENT_MSG);
  std::vector<int> * ndxs = new std::vector<int>();
  ndxs->reserve(sim_get_length(indexes));
  for (unsigned i = 1; i <= sim_get_length(indexes); ++i) ndxs->push_back(int(sim_array_get_number(indexes, i)));

  QCoreApplication::postEvent(SimCore::get(), new MessageSentEvent(id, ndxs, notsent ? 'n' : 'u'));
}

extern "C" void handleSIMeventXFER(simnumber id, const simtype event)
{
  if (g_oldSIMeventHandlers[simev_xfer]) g_oldSIMeventHandlers[simev_xfer](id, event);

  QString qs;
  const char * oldType = sim_table_get_pointer(event, SIM_EVENT_XFER);
  const char * newType = sim_table_get_pointer(event, SIM_CMD_XFER_SEND_TYPE);
  simnumber xferId = sim_table_get_number(event, SIM_CMD_XFER_SEND_HANDLE);

  if (xferId == SIM_XFER_GET_RECEIVED || xferId == SIM_XFER_GET_SENT) {
    QCoreApplication::postEvent(SimCore::get(), new TransferEvent(id, xferId, oldType, newType));
  } else if (*oldType || *newType) {
    if (*oldType && *newType && Transfers::getTransferState(oldType) == Transfers::getTransferState(newType)) return;
    if (!*newType) xferId ^= simnumber(1) << 62;
    QCoreApplication::postEvent(SimCore::get(), new TransferStateEvent(id, xferId, *newType ? newType : oldType));
  } else if (xferId < 0) {
    qs.sprintf("%019lld.temp", -xferId);
    QCoreApplication::postEvent(SimCore::get(), new TransferEvent(id, SIM_XFER_GET_RECEIVED, qs, qs));
    qs.sprintf("%019lld.part", -xferId);
    QCoreApplication::postEvent(SimCore::get(), new TransferEvent(id, SIM_XFER_GET_RECEIVED, qs, qs));
  } else {
    qs.sprintf("%019lld.temp", xferId);
    QCoreApplication::postEvent(SimCore::get(), new TransferEvent(id, SIM_XFER_GET_RECEIVED, qs, ""));
    qs.sprintf("%019lld.part", xferId);
    QCoreApplication::postEvent(SimCore::get(), new TransferEvent(id, SIM_XFER_GET_RECEIVED, qs, ""));
  }
}

static SimAudioState convertStringToAudio(const char * audioState)
{
  if (audioState) {
    if (!strcmp(audioState, CONTACT_AUDIO_RINGING)) return audio_ringing;
    if (!strcmp(audioState, CONTACT_AUDIO_CALLING)) return audio_connect;
    if (!strcmp(audioState, CONTACT_AUDIO_OUTGOING)) return audio_outgoing;
    if (!strcmp(audioState, CONTACT_AUDIO_INCOMING)) return audio_incoming;
    if (!strcmp(audioState, CONTACT_AUDIO_TALKING)) return audio_talk;
    if (!strcmp(audioState, CONTACT_AUDIO_UDP)) return audio_udp;
    if (!strcmp(audioState, CONTACT_AUDIO_HANGUP)) return audio_hangup;
  }
  return audio_none;
}

extern "C" void handleSIMeventAUDIO(simnumber id, const simtype event)
{
  if (g_oldSIMeventHandlers[simev_audio]) g_oldSIMeventHandlers[simev_audio](id, event);

  const char * newAudioState = sim_table_get_pointer(event, CONTACT_KEY_AUDIO);
  const char * oldAudioState = sim_table_get_pointer(event, SIM_EVENT_AUDIO);

  SimEventBase * ev = new AudioEvent(id, convertStringToAudio(oldAudioState), convertStringToAudio(newAudioState));
  QCoreApplication::postEvent(SimCore::get(), ev);
}

extern "C" void handleSIMeventSPEECH(simnumber id, const simtype event)
{
  if (g_oldSIMeventHandlers[simev_speech]) g_oldSIMeventHandlers[simev_speech](id, event);

  int err = int(sim_table_get_number(event, SIM_EVENT_SPEECH));
  int prob = int(sim_table_get_number(event, SIM_EVENT_SPEECH_PROBABILITY));
  int percent = int(sim_table_get_number(event, SIM_EVENT_SPEECH_PROGRESS));

  QCoreApplication::postEvent(SimCore::get(), new SpeechEvent(id, err, prob, percent));
}

extern "C" void handleSIMeventKEYGEN(simnumber id, const simtype event)
{
  if (g_oldSIMeventHandlers[simev_keygen]) g_oldSIMeventHandlers[simev_keygen](id, event);

  int err = int(sim_table_get_number(event, SIM_EVENT_KEYGEN));
  simnumber time2gen = sim_table_get_number(event, SIM_EVENT_KEYGEN_TIME);
  const char * address = sim_table_get_pointer(event, SIM_EVENT_KEYGEN_ADDRESS);

  QCoreApplication::postEvent(SimCore::get(), new KeygenEvent(id, err, time2gen, address));
}

extern "C" void handleSIMeventHISTORY(simnumber id, const simtype msg)
{
  if (g_oldSIMeventHandlers[simev_history]) g_oldSIMeventHandlers[simev_history](id, msg);

  const char * text = sim_table_get_pointer(msg, SIM_CMD_MSG_TEXT);
  int msgNdx = int(sim_table_get_number(msg, SIM_EVENT_HISTORY));
  int status = int(sim_table_get_number(msg, SIM_CMD_MSG_STATUS));

  //log_note_("ui", "%d, %s\n", (int)status, text);
  if (!SIM_STRING_CHECK_DIFF_CONST(text, "FILE ")) {
    if (const char * s = strchr(text += SIM_STRING_GET_LENGTH_CONST("FILE "), ' ')) {
      simnumber xferId = sim_table_get_number(msg, SIM_CMD_MSG_HANDLE) ^ simnumber(status != SIM_MSG_INCOMING) << 62;
      simnumber xferTs = sim_table_get_number(msg, SIM_CMD_MSG_RECEIVED);
      int simres = SIM_STRING_CHECK_DIFF_CONST(text, "SEND ") ? atoi(s + 1) : SIM_XFER_BAD_HANDLE;
      QString qs = QString(text).left(s - text + 1);
      if (simres == SIM_OK && SIM_STRING_CHECK_DIFF_CONST(text, "RECV ")) simres = SIM_XFER_CANCELLED;
      qs.append(QString::number(simres)).append(strchr(s + 1, ' '));
      QCoreApplication::postEvent(SimCore::get(), new TransferEvent(id, xferId, QString::number(xferTs), qs));
    }
  } else if (!strcmp(text, "CALL ABORT") || !strcmp(text, "CALL HANGUP") || !strcmp(text, "CALL HUNGUP")) {
    QCoreApplication::postEvent(SimCore::get(), new AudioEvent(id, audio_none, audio_abort));
  } else if (!SIM_STRING_CHECK_DIFF_CONST(text, "CALL HANGUP ") || !SIM_STRING_CHECK_DIFF_CONST(text, "CALL HUNGUP ")) {
    QCoreApplication::postEvent(SimCore::get(), new AudioEvent(id, audio_none, audio_disconnect));
  } else if (status == SIM_MSG_INCOMING) {
    if (!SIM_STRING_CHECK_DIFF_CONST(text, "CALL BUSY ")) {
      QCoreApplication::postEvent(SimCore::get(), new AudioEvent(id, audio_none, audio_failed));
    } else if (!strcmp(text, "CALL BUSY")) {
      QCoreApplication::postEvent(SimCore::get(), new AudioEvent(id, audio_none, audio_busy));
    }
  } else {
    if (!SIM_STRING_CHECK_DIFF_CONST(text, "CALL FAILED ") || !SIM_STRING_CHECK_DIFF_CONST(text, "CALL ABORT ")) {
      QCoreApplication::postEvent(SimCore::get(), new AudioEvent(id, audio_none, audio_failed));
    } else if (!strcmp(text, "CALL BUSY") || !strcmp(text, "CALL FAILED")) {
      QCoreApplication::postEvent(SimCore::get(), new AudioEvent(id, audio_none, audio_abort));
    }
  }

  if (status == SIM_MSG_INCOMING) { // this must be done after sending the previous events because they clear flag_missedCall
    if (!strcmp(text, "CALL ABORT") || !SIM_STRING_CHECK_DIFF_CONST(text, "CALL ABORT ")) {
      QCoreApplication::postEvent(SimCore::get(), new AudioEvent(id, audio_incoming, audio_missed));
    } else if (!strcmp(text, "CALL FAILED") || !SIM_STRING_CHECK_DIFF_CONST(text, "CALL FAILED ")) {
      QCoreApplication::postEvent(SimCore::get(), new AudioEvent(id, audio_none, audio_missed));
    }
  }

  if (!strcmp(text, "STATUS OFF")) {
    QCoreApplication::postEvent(SimCore::get(), new ContactStatusChangedEvent(0, SIM_STATUS_ON, SIM_STATUS_OFF));
    QCoreApplication::postEvent(SimCore::get(), new ErrorEvent(0, 'u', 0, 0));
  } else if (!strcmp(text, "STATUS ON")) {
    QCoreApplication::postEvent(SimCore::get(), new ContactStatusChangedEvent(id, SIM_STATUS_OFF, SIM_STATUS_ON));
  }

  if (msgNdx) QCoreApplication::postEvent(SimCore::get(), new MessageReceivedEvent(id, msgNdx, true));
}

extern "C" void handleSIMeventNET(simnumber id, const simtype event)
{
  if (g_oldSIMeventHandlers[simev_net]) g_oldSIMeventHandlers[simev_net](id, event);

  simtype type = sim_table_get_string(event, SIM_EVENT_NET);
  if (!sim_string_check_diff(type, SIM_EVENT_NET_STATUS)) {
    int status = sim_table_get_number(event, SIM_EVENT_NET_STATUS) <= 0;
    QCoreApplication::postEvent(SimCore::get(), new NetEvent(id, 'h', status, 0));
  } else if (!sim_string_check_diff(type, SIM_EVENT_NET_DHT)) {
    int status = int(sim_table_get_number(event, SIM_EVENT_NET_DHT));
    QCoreApplication::postEvent(SimCore::get(), new NetEvent(id, 'H', status, 0));
  } else if (!sim_string_check_diff(type, SIM_EVENT_NET_LIMIT)) {
    char errbuf[SIM_SIZE_ERROR];
    int err = int(sim_table_get_number(event, SIM_EVENT_NET_LIMIT));
    SimEventBase * ev = new NetEvent(id, 'L', err, SimCore::getErrorBuffer(err, errbuf));
    QCoreApplication::postEvent(SimCore::get(), ev);
  } else if (!sim_string_check_diff(type, SIM_EVENT_NET_DEVICE)) {
    const char * output = sim_table_get_pointer(event, SIM_EVENT_NET_DEVICE_OUTPUT);
    const char * input = sim_table_get_pointer(event, SIM_EVENT_NET_DEVICE_INPUT);
    const char * ring = sim_table_get_pointer(event, SIM_EVENT_NET_DEVICE_RING);
    QCoreApplication::postEvent(SimCore::get(), new InvalidDeviceEvent(output, input, ring));
  } else if (!sim_string_check_diff(type, SIM_EVENT_NET_QOS)) {
    QCoreApplication::postEvent(SimCore::get(), new NetEvent(id, 'q', 0, 0));
  } else if (!sim_string_check_diff(type, SIM_EVENT_NET_PONG)) {
    QCoreApplication::postEvent(SimCore::get(), new NetEvent(id, 'o', 0, 0));
  } else if (!sim_string_check_diff(type, SIM_EVENT_NET_DISCONNECT)) {
    int err = int(sim_table_get_number(event, SIM_EVENT_NET_DISCONNECT));
    QCoreApplication::postEvent(SimCore::get(), new NetEvent(id, 'd', err, 0));
  } else if (!sim_string_check_diff(type, SIM_EVENT_NET_CONNECT)) {
    if (sim_table_get_number(event, SIM_EVENT_NET_CONNECT) == SIM_OK) {
      g_socksErrorReceived = false;
      QCoreApplication::postEvent(SimCore::get(), new NetEvent(id, 'c', SIM_OK, 0));
    }
  } else if (!sim_string_check_diff(type, SIM_EVENT_NET_TRAVERSE)) {
    int err = int(sim_table_get_number(event, SIM_EVENT_NET_TRAVERSE));
    QCoreApplication::postEvent(SimCore::get(), new NetEvent(id, 't', err, 0));
  }
}

static char convertStringToError(const char * errorType)
{
  if (errorType) {
    if (!strcmp(errorType, SIM_EVENT_ERROR_INSTALL)) return 'i';
    if (!strcmp(errorType, SIM_EVENT_ERROR_SOCKS)) return 'S';
    if (!strcmp(errorType, SIM_EVENT_ERROR_SYSTEM)) return 'm';
    if (!strcmp(errorType, SIM_EVENT_ERROR_CIPHER)) return 'h';
    if (!strcmp(errorType, SIM_EVENT_ERROR_PROTOCOL)) return 'p';
    if (!strcmp(errorType, SIM_EVENT_ERROR_KEY)) return 'k';
    if (!strcmp(errorType, SIM_EVENT_ERROR_XFER)) return 'x';
    if (!strcmp(errorType, SIM_EVENT_ERROR_AUDIO)) return 'a';
    if (!strcmp(errorType, SIM_EVENT_ERROR_INIT_LOCAL)) return 'L';
    if (!strcmp(errorType, SIM_EVENT_ERROR_FILE_CREATE)) return 'c';
    if (!strcmp(errorType, SIM_EVENT_ERROR_FILE_OPEN)) return 'o';
    if (!strcmp(errorType, SIM_EVENT_ERROR_FILE_CLOSE)) return 'e';
    if (!strcmp(errorType, SIM_EVENT_ERROR_FILE_LOAD)) return 'l';
    if (!strcmp(errorType, SIM_EVENT_ERROR_FILE_SAVE)) return 's';
    if (!strcmp(errorType, SIM_EVENT_ERROR_FILE_WRITE)) return 'w';
    if (!strcmp(errorType, SIM_EVENT_ERROR_FILE_DELETE)) return 'd';
    if (!strcmp(errorType, SIM_EVENT_ERROR_FILE_WIPE)) return 'W';
    if (!strcmp(errorType, SIM_EVENT_ERROR_FILE_RENAME)) return 'r';
    if (!strcmp(errorType, SIM_EVENT_ERROR_FILE_COPY)) return 'y';
  }
  return 0;
}

static QString convertErrorToString(char code, Contact * c)
{
  if (c) {
    switch (code) {
      case 'c': return SimCore::tr("Creating a file for %1 was not successful (%2)");
      case 'e': return SimCore::tr("Closing a file for %1 was not successful (%2)");
      case 'l': return SimCore::tr("Loading a file for %1 was not successful (%2)");
      case 's': return SimCore::tr("Saving a file for %1 was not successful (%2)");
      case 'w': return SimCore::tr("Writing to a file for %1 was not successful (%2)");
      case 'd': return SimCore::tr("Deleting a file for %1 was not successful (%2)");
      case 'W': return SimCore::tr("Erasing a file for %1 was not successful (%2)");
      case 'r': return SimCore::tr("Renaming a file for %1 was not successful (%2)");
      case 'y': return SimCore::tr("Copying a file for %1 was not successful (%2)");
    }
  } else {
    switch (code) {
      case 'c': return SimCore::tr("Creating a file was not successful (%1)");
      case 'e': return SimCore::tr("Closing a file was not successful (%1)");
      case 'l': return SimCore::tr("Loading a file was not successful (%1)");
      case 's': return SimCore::tr("Saving a file was not successful (%1)");
      case 'w': return SimCore::tr("Writing to a file was not successful (%1)");
      case 'd': return SimCore::tr("Deleting a file was not successful (%1)");
      case 'W': return SimCore::tr("Erasing a file was not successful (%1)");
      case 'r': return SimCore::tr("Renaming a file was not successful (%1)");
      case 'y': return SimCore::tr("Copying a file was not successful (%1)");
    }
  }
  return QString();
}

extern "C" void handleSIMeventERROR(simnumber id, const simtype event)
{
  if (g_oldSIMeventHandlers[simev_error]) g_oldSIMeventHandlers[simev_error](id, event);

  char errbuf[SIM_SIZE_ERROR];
  const char * type = sim_table_get_pointer(event, SIM_EVENT_ERROR);
  char code = convertStringToError(type);

  if (code == 'm' || code == 'h') {
    QCoreApplication::postEvent(SimCore::get(), new ErrorEvent(id, code, 0, sim_table_get_pointer(event, type)));
  } else if (code) {
    if (code == 'S') {
      if (g_socksErrorReceived) return;
      g_socksErrorReceived = true;
    }

    int err = code == 'k' ? SIM_CRYPT_BAD_KEY : int(sim_table_get_number(event, type));
    SimEventBase * ev = new ErrorEvent(id, code, err, SimCore::getErrorBuffer(err, errbuf));
    QCoreApplication::postEvent(SimCore::get(), ev);
  }
}

extern "C" void handleSIMeventLOG(simnumber id, const simtype event)
{
  if (g_oldSIMeventHandlers[simev_log]) g_oldSIMeventHandlers[simev_log](id, event);

  int level = SIM_LOG_UNKNOWN;

  if (SimCore::mc_popup) {
    simtype text = sim_table_get_string(event, SIM_CMD_MSG_TEXT);

    level = sim_log_get_level(&text);
  }

  QCoreApplication::postEvent(SimCore::get(), new LogEvent(level));
}

extern "C" void handleSIMeventTICK(simnumber id, const simtype event)
{
  if (g_oldSIMeventHandlers[simev_tick]) g_oldSIMeventHandlers[simev_tick](id, event);

#ifdef HAVE_LIBPTH
  QAbstractEventDispatcher * dispatcher = QAbstractEventDispatcher::instance();

  if (dispatcher) {
    while (dispatcher->processEvents(QEventLoop::AllEvents)) {
#ifdef __APPLE__
      break;
#endif
    }
  }
#else
  g_tickEvent = sim_table_get_pointer(event, SIM_EVENT_TICK);
#endif
}

void SimThread::run()
{
  QString qs;
  switch (m_action) {
    case thread_login:
      g_tickEvent = 0;
      qs = Login::translateKey(m_argStr);
      m_simres = sim_init_user_(m_argStr.isNull() ? 0 : qs.toUtf8().data());
      if (m_simres != SIM_OK && qs != m_argStr && !m_argStr.isNull()) {
        m_simres = sim_init_user_(m_argStr.toUtf8().data());
      }

      if (m_simres == SIM_OK && Contact::findCountry(0xFFFFFFFF).compare(Contact::tr("unknown"))) {
        sim_exit_user_();
        m_simres = SIM_API_EXIT;
      }
      break;

    case thread_logout:
    case thread_quit:
      m_simres = sim_exit_user_();

      if (m_simres != SIM_OK) {
        log_warn_("ui", "%s error %d\n", __FUNCTION__, m_simres);
        _exit(EXIT_FAILED_THREAD);
      }
  }
  char * simerr = sim_error_get_text(m_simres);
  m_argStr = simerr;
  sim_free_string(simerr);
  sim_error_get(m_simres); // free error buffer for this thread
}

SimCore * SimCore::mc_simcore = 0;
QString SimCore::mc_startupUser = "";
QString SimCore::mc_startupPass;
QString SimCore::mc_keygenPass;
QString SimCore::mc_lastPass;
bool SimCore::mc_calling = false;
char SimCore::mc_callConnection;
simnumber SimCore::mc_callTime;
bool SimCore::mc_rpc = false;
bool SimCore::mc_popup = false;
bool SimCore::mc_autolog;
bool SimCore::mc_startupMinimized = false;
bool SimCore::mc_startupOffline = false;
bool SimCore::mc_startupSocks = false;
bool SimCore::mc_hasAudioChanged = false;
int SimCore::mc_hasSocksError = 0;
int SimCore::mc_hasProtocolError = 0;
int SimCore::mc_hasXferError = 0;
simnumber SimCore::mc_hasXferErrorId = 0;
char SimCore::mc_hidden = 0;

SimCore::SimCore()
  : m_keygenDialog(0)
  , m_simThread(0)
  , m_hotKeyThread(0)
  , m_logged(false)
  , m_notifications(0)
  , m_myId(-1)
  , m_hwnd(0)
  , m_speechProb(-1)
  , m_speechDialog(0)
  , m_login(0)
  , m_latencyDialog(0)
  , m_latencyId(0)
  , m_latencyCount(0)
  , m_latencyClosed(false)
  , m_latencyFilter(1)
  , m_latencyDelay(2000)
  , m_hangupData(0)
{
  m_hotKey[1] = m_hotKey[0] = 0;
  mc_simcore = this;
  //log_note_("ui", "SimCore()\n");
}

SimCore::~SimCore()
{
  mc_simcore = 0;
  //delete m_keygenDialog;
  //log_note_("ui", "~SimCore()\n");
}

int SimCore::create()
{
  if (mc_simcore) return SIM_OK;
  mc_autolog = true;

  QString ver;

#if SIM_CORE_REVISION
  ver.sprintf("%s %d:0x%06x Qt-", SIM_UI_NAME, SIM_CORE_REVISION, unsigned(SIM_CORE_BUILD >> 24));
#else
  ver = SIM_UI_NAME " " PACKAGE_VERSION " Qt-";
#endif
  ver.append(qtfix::getVersion());
#ifdef SIM_QT_XCB_NO_SM
  ver.append(" noSM");
#endif

  int simres = sim_init_(mc_startupUser.isNull() ? 0 : mc_startupUser.toUtf8().data(), 0, ver.toUtf8().data());
  while (simres != SIM_OK && mc_hidden != 'h' && showLoginError(simres) == 1) {
    simres = sim_init_(mc_startupUser.isNull() ? 0 : mc_startupUser.toUtf8().data(), 0, ver.toUtf8().data());
  }

  if (simres == SIM_OK) {
#ifdef _DEBUG
    mc_popup = mc_rpc = true;
#else
    mc_rpc = SimParam::get("ui.main.rpc");
    mc_popup = SimParam::get("ui.console.popup");
#endif
    if (mc_startupUser.isNull()) SimParam::set("ui.console.unload", -1, true);

    mc_simcore = new SimCore();
    connect(&mc_simcore->m_coreTimer, SIGNAL(timeout()), mc_simcore, SLOT(onTimerTimeout()));
    connect(&mc_simcore->m_speechTimer, SIGNAL(timeout()), mc_simcore, SLOT(onSpeechTimeout()));
    connect(&mc_simcore->m_latencyTimer, SIGNAL(timeout()), mc_simcore, SLOT(onLatencyTimeout()));
    connect(&mc_simcore->m_hangupTimer, SIGNAL(timeout()), mc_simcore, SLOT(onHangupTimeout()));

    sim_event_register_(SIM_EVENT_FATAL, handleSIMeventFATAL, &g_oldSIMeventHandlers[simev_fatal]);
    sim_event_register_(SIM_EVENT_CONTACT, handleSIMeventCONTACT, &g_oldSIMeventHandlers[simev_contact]);
    sim_event_register_(SIM_EVENT_STATUS, handleSIMeventSTATUS, &g_oldSIMeventHandlers[simev_status]);
    sim_event_register_(SIM_EVENT_MSG, handleSIMeventMSG, &g_oldSIMeventHandlers[simev_message]);
    sim_event_register_(SIM_EVENT_EDIT, handleSIMeventEDIT, &g_oldSIMeventHandlers[simev_edit]);
    sim_event_register_(SIM_EVENT_SENT, handleSIMeventSENT, &g_oldSIMeventHandlers[simev_msgsent]);
    sim_event_register_(SIM_EVENT_NOTSENT, handleSIMeventNOTSENT, &g_oldSIMeventHandlers[simev_notsent]);
    sim_event_register_(SIM_EVENT_XFER, handleSIMeventXFER, &g_oldSIMeventHandlers[simev_xfer]);
    sim_event_register_(SIM_EVENT_AUDIO, handleSIMeventAUDIO, &g_oldSIMeventHandlers[simev_audio]);
    sim_event_register_(SIM_EVENT_SPEECH, handleSIMeventSPEECH, &g_oldSIMeventHandlers[simev_speech]);
    sim_event_register_(SIM_EVENT_KEYGEN, handleSIMeventKEYGEN, &g_oldSIMeventHandlers[simev_keygen]);
    sim_event_register_(SIM_EVENT_HISTORY, handleSIMeventHISTORY, &g_oldSIMeventHandlers[simev_history]);
    sim_event_register_(SIM_EVENT_NET, handleSIMeventNET, &g_oldSIMeventHandlers[simev_net]);
    sim_event_register_(SIM_EVENT_ERROR, handleSIMeventERROR, &g_oldSIMeventHandlers[simev_error]);
    sim_event_register_(SIM_EVENT_LOG, handleSIMeventLOG, &g_oldSIMeventHandlers[simev_log]);
    sim_event_register_(SIM_EVENT_TICK, handleSIMeventTICK, &g_oldSIMeventHandlers[simev_tick]);
  }
  return simres;
}

void SimCore::destroy()
{
#ifdef HAVE_LIBXCB
  if (mc_simcore->m_hotKeyThread) mc_simcore->m_hotKeyThread->uninit();
#endif
  if (sim_exit_() == SIM_OK && m_simThread) m_simThread->wait();
  delete mc_simcore;
}

QString SimCore::getError(int simres)
{
  char * s = simres < 0 ? sim_error_get_text(simres) : 0;
  return getErrorString(simres, sim_error_get(simres), s);
}

QString SimCore::getErrorBuffer(int simres, char * buffer, bool peek)
{
  char * s = simres < 0 ? sim_error_get_text(simres) : 0;
  return getErrorString(simres, buffer ? sim_error_get_buffer(simres, peek, buffer) : sim_error_peek(simres), s);
}

QString SimCore::getErrorString(int simres, const char * error, char * text)
{
  QString qs;
  if (error) {
    if (!SIM_STRING_CHECK_DIFF_CONST(error, "error number ")) {
      qs = qApp->translate("SimError", "error number ").append(error + SIM_STRING_GET_LENGTH_CONST("error number "));
    } else if (simres < 0) {
      int len = strlen(error);
      int l = text ? strlen(text) : 0;
      if (len > l) len -= l;

      QByteArray bytes(error, len);
      qs = qApp->translate("SimError", bytes.data()).append(error + len);
    } else {
      qs = error;
    }
    qs = QLocale().quoteString(qs);
  }
  sim_free_string(text);
  return qs;
}

int SimCore::getContactId(simnumber simId, bool doNotCreate)
{
  int id = m_idMap[simId];
  if (id) return id - 1;

  if (doNotCreate) return -1;

  int result = int(m_contacts.size());
  m_contacts.push_back(new Contact(simId, result));
  m_idMap[simId] = result + 1;
  emit signalContactAdded(1);
  return result;
}

int SimCore::getTestContactId()
{
  int id = getContactId(CONTACT_ID_TEST, true);
  if (id < 0) {
    id = getContactId(CONTACT_ID_TEST, false);
    if (id >= 0) {
      Contact * c = getContact(id);
      if (c) {
        c->setTestContact(true);
        c->setVerified();
        c->setVerify(true);
        c->m_nick = "console";
      }
    }
  }
  return id;
}

void SimCore::getUserContact(simnumber simId, int myStatus)
{
  simtype contact = sim_contact_get_(simId, CONTACT_BIT_LINE);
  if (!sim_get_pointer(contact)) return; // impossible

  int auth = int(sim_table_get_number(contact, CONTACT_KEY_AUTH));
  simtype vip = sim_table_get_string(contact, CONTACT_KEY_VIP);
  Contact * c;

  if (sim_string_check_diff(vip, CONTACT_VIP_TEST) || getTestContactId() < 0) {
    c = getContactId(simId);
  } else {
    c = getContact(getTestContactId());
    c->setTestContact(true);
  }
  c->setMyself(!sim_string_check_diff(vip, CONTACT_VIP_MYSELF));
  c->setSystem(!sim_string_check_diff(vip, CONTACT_VIP_SYSTEM));
  c->setForgotten(auth == CONTACT_AUTH_FORGET);
  c->setDeleted(auth == CONTACT_AUTH_DELETED || auth == CONTACT_AUTH_REVOKED);
  c->setBlocked(auth == CONTACT_AUTH_BLOCKED);
  c->setNewContact(auth == CONTACT_AUTH_NEW);

  c->m_rights = sim_table_get_number(contact, CONTACT_KEY_FLAGS);
  if (c->m_rights & CONTACT_FLAG_VERIFIED) c->setVerified();
  c->setVerify((c->m_rights & CONTACT_FLAG_VERIFY) != 0);
  c->m_nick = qtfix::getString(sim_table_get_pointer(contact, CONTACT_KEY_NICK));
  c->m_info = qtfix::getStrings(sim_table_get_pointer(contact, CONTACT_KEY_LINE));
  c->m_editMax = int(sim_table_get_number(contact, CONTACT_KEY_EDIT));

  int status = int(sim_table_get_number(contact, CONTACT_KEY_STATUS));
  if (c->isMe()) {
    unsigned flags = 0;
    status = getMyStatus(&flags);
    if (!(flags & SIM_STATUS_FLAG_DHT_OUT)) {
      status = SIM_STATUS_OFF;
    } else if (status == SIM_STATUS_IDLE) {
      status = SIM_STATUS_AWAY;
    }
    m_myId = c->m_id;
  } else if (myStatus == SIM_STATUS_OFF && !c->isTest()) {
    status = SIM_STATUS_OFF;
  }
  c->setStatus(short(status));
  emitContactChanged(c->m_id);
  sim_contact_free_(contact);
}

void SimCore::getUserContacts()
{
  int myStatus = getMyStatus();
  simtype contacts = sim_contact_get_(0, CONTACT_BIT_DEFAULT);
  for (unsigned i = 1; i <= sim_get_length(contacts); i++) {
    getUserContact(sim_array_get_number(contacts, i), myStatus);
  }
  sim_contact_free_(contacts);
}

SimCore::E_ConnectionState SimCore::getConnectionState(int id)
{
  E_ConnectionState state = state_disconnect;
  Contact * c = id < 0 ? 0 : getContact(id);

  if (c) {
    if (!c->isTest()) {
      simtype contact = sim_contact_get_(c->m_simId, CONTACT_BIT_DEFAULT | CONTACT_BIT_STATS);
      simtype ip = sim_table_get_string(contact, CONTACT_KEY_CONNECT);
      if (sim_get_pointer(ip)) {
        if (!strcmp(sim_get_pointer(ip), "IN")) {
          state = state_connected_in;
        } else if (!strcmp(sim_get_pointer(ip), "REVERSE")) {
          state = state_reversed_in;
        } else if (!strcmp(sim_get_pointer(ip), "DIRECT") || !strcmp(sim_get_pointer(ip), "LOCAL")) {
          state = state_reversed_out;
        } else if (!strcmp(sim_get_pointer(ip), "TRAVERSE")) {
          state = state_traversed_in;
        } else if (sim_get_length(ip) == SIM_STRING_GET_LENGTH_CONST("OUTx") &&
                   !SIM_STRING_CHECK_DIFF_CONST(sim_get_pointer(ip), "OUT")) {
          state = state_traversed_out;
        } else {
          state = state_connected_out;
        }
        if (strchr(sim_get_pointer(ip), '/')) {
          state = c->hasTraversed() ? state_relayed_out : state_relaying_out;
          if (!sim_string_check_diff(sim_table_get_string(contact, CONTACT_KEY_AUDIO), CONTACT_AUDIO_UDP)) {
            state = state_traversed_udp;
          } else if (!SIM_STRING_CHECK_DIFF_CONST(sim_get_pointer(ip), "IN/") ||
                     !SIM_STRING_CHECK_DIFF_CONST(sim_get_pointer(ip), "OUT/")) {
            bool ok = true;
            for (char * s = strchr(sim_get_pointer(ip), '/') + 1; *s; s++) {
              if ((*s < '0' || *s > '9') && (*s != '.')) ok = false;
            }
            if (ok && strlen(strchr(sim_get_pointer(ip), '/') + 1)) {
              if (!SIM_STRING_CHECK_DIFF_CONST(sim_get_pointer(ip), "IN/")) {
                state = c->hasTraversed() ? state_relayed_in : state_relaying_in;
              }
            }
          }
        } else if (!sim_string_check_diff(sim_table_get_string(contact, CONTACT_KEY_AUDIO), CONTACT_AUDIO_UDP)) {
          state = state_connected_udp;
        }
      }
      sim_contact_free_(contact);
    } else {
      if (g_DHTstatus >= 0) state = E_ConnectionState(g_DHTstatus);
    }
  }
  return state;
}

QString SimCore::getMyNick() const
{
  if (Contact * contact = getContact()) return contact->m_nick;
  return "";
}

void SimCore::getMyContact()
{
  if (Contact * contact = getContact()) {
    getUserContact(contact->m_simId, getMyStatus());
    emitContactChanged(contact->m_id);
  }
}

int SimCore::getMyStatus(unsigned * flags)
{
  return sim_status_get_(flags);
}

int SimCore::setMyStatus(int status)
{
  return sim_status_set_(status);
}

void SimCore::setLoginDialog(LoginDialog * login)
{
  if (m_login && login && login != m_login) {
    log_error_("ui", "dialog %s -> %s\n", m_login->objectName().toUtf8().data(), login->objectName().toUtf8().data());
  }
  m_login = login;
}

// Called when hidden is shown
void SimCore::unhide()
{
#ifdef __APPLE__
  if (mc_hidden != 's') {
    ProcessSerialNumber psn = { 0, kCurrentProcess };
    OSStatus status = TransformProcessType(&psn, kProcessTransformToForegroundApplication);

    if (status && mc_simcore) {
      QString error = tr("Exiting stealth mode not successful (error %1)").arg(status);
      log_error_("ui", "%s failed\n", __FUNCTION__);
      execMessageBox(true, error, tr("Simphone could not transform."));
    }
  }
#endif
  mc_hidden = 's';

  if (mc_hasProtocolError) SoundEffects::playProtocolSound(-1, mc_hasProtocolError);
  mc_hasProtocolError = 0;

  if (mc_simcore) {
    std::vector<Contact *> & contacts = mc_simcore->m_contacts;
    for (unsigned i = contacts.size(); i--;) {
      if (Contact * c = contacts[i]) {
        QSetIterator<int> iterator(c->getCryptoErrors(true));

        c->clearCryptoErrors(true);
        while (iterator.hasNext()) { // replays the protocol sound once more per contact but so what
          int err = iterator.next();
          SimEventBase * ev = new ErrorEvent(c->m_simId, err == SIM_CRYPT_BAD_KEY ? 'k' : 'p', err, 0);
          QCoreApplication::postEvent(mc_simcore, ev);
        }
      }
    }

    if (mc_hasSocksError) {
      if (g_socksErrorReceived) {
        SimEventBase * ev = new ErrorEvent(0, 'S', mc_hasSocksError, getErrorBuffer(mc_hasSocksError));
        QCoreApplication::postEvent(mc_simcore, ev);
      }
      g_socksErrorReceived = false;
      mc_hasSocksError = 0;
    }

    if (mc_hasXferError) {
      SimEventBase * ev = new ErrorEvent(mc_hasXferErrorId, 'x', mc_hasXferError, getErrorBuffer(mc_hasXferError));
      QCoreApplication::postEvent(mc_simcore, ev);
      mc_hasXferError = 0;
    }

    if (mc_hasAudioChanged) {
      mc_hasAudioChanged = false;
      Settings::setInvalidDevices(false, false, false);
      QCoreApplication::postEvent(mc_simcore, new InvalidDeviceEvent("INVALID", "INVALID", "INVALID"));
    }

    for (int i = 0; i < g_fileErrorsReceived.size(); i++) {
      ErrorEvent * ev = (ErrorEvent *)g_fileErrorsReceived[i];
      QCoreApplication::postEvent(mc_simcore, new ErrorEvent(ev->m_simId, ev->m_code, ev->m_err, ev->m_name));
      delete ev;
    }
    g_fileErrorsReceived = QList<SimEventBase *>();
  }
}

void SimCore::hide()
{
#ifdef __APPLE__
#define kProcessTransformToBackgroundApplication 2
  if (mc_hidden != 'h') {
    ProcessSerialNumber psn = { 0, kCurrentProcess };
    OSStatus status = TransformProcessType(&psn, kProcessTransformToBackgroundApplication);

    if (status && mc_simcore) {
      QString error = tr("Entering stealth mode not successful (error %1)").arg(status);
      log_error_("ui", "%s failed\n", __FUNCTION__);
      execMessageBox(true, error, tr("Simphone could not transform."));
    }
  }
#endif
  mc_hidden = 'h';
}

void SimCore::customEvent(QEvent * event)
{
  if (m_simThread) {
    if (int(event->type()) != SimEventBase::event_log) {
      log_debug_("ui", "event unexpected (type = %d)\n", int(event->type()));
    }
    if (m_simThread->m_action != SimThread::thread_login) return;
  }

  switch (int(event->type())) {
    case SimEventBase::event_contact:
      if (m_logged) {
        ContactChangedEvent * econtact = (ContactChangedEvent *)event;
        log_debug_("ui", "event contact @%020llu\n", econtact->m_simId);
        getUserContacts();
        if (econtact->m_simId) {
          QString message = (QString("0").repeated(20) + QString::number(qulonglong(econtact->m_simId))).right(20);
          int id = getContactId(econtact->m_simId, true);
          Contact * c = id < 0 ? 0 : getContact(id);
          if (c) {
            c->setNotifications(Contact::flag_hasNewContact);
            message = c->m_nick;
          }
          Contacts::get()->showStatus(tr("%1 has requested contact with you.").arg(message));
          SoundEffects::playNewContactSound(id); // new contact request.
        }
      }
      break;

    case SimEventBase::event_status:
      if (m_logged) {
        ContactStatusChangedEvent * estatus = (ContactStatusChangedEvent *)event;
        if (estatus->m_simId) {
          Contact * c = getContactId(estatus->m_simId);
          if (c) {
            log_info_("ui", "status change @%020llu from %d to %d\n",
                      estatus->m_simId, estatus->m_oldStatus, estatus->m_newStatus);
            if (!c->isMe()) c->setStatus(short(estatus->m_newStatus));
            emit signalContactStatusChanged(c->m_id, estatus->m_oldStatus, estatus->m_newStatus);
          } else {
            log_error_("ui", "status change @%020llu from %d to %d\n",
                       estatus->m_simId, estatus->m_oldStatus, estatus->m_newStatus);
          }
        } else {
          Contacts::get()->setMyStatus(getMyStatus());
          emit signalContactStatusChanged(m_myId, estatus->m_oldStatus, estatus->m_newStatus);
        }
      }
      break;

    case SimEventBase::event_flags:
      if (m_logged) {
        ContactFlagsChangedEvent * eflags = (ContactFlagsChangedEvent *)event;
        Contact * c = getContactId(eflags->m_simId);
        if (c) {
          const int flags = CONTACT_FLAG_VERIFY | CONTACT_FLAG_UTF | CONTACT_FLAG_XFER | CONTACT_FLAG_AUDIO;
          log_info_("ui", "flags change @%020llu to %X\n", eflags->m_simId, eflags->m_flags);
          c->m_rights = (c->m_rights & ~flags) | (eflags->m_flags & flags);
          c->setTypingFlag((eflags->m_flags & CONTACT_FLAG_TYPE) != 0);
          c->setVerify((eflags->m_flags & CONTACT_FLAG_VERIFY) != 0);
          emitContactChanged(c->m_id);
        } else {
          log_error_("ui", "flags change @%020llu to flags %d\n", eflags->m_simId, eflags->m_flags);
        }
      }
      break;

    case SimEventBase::event_info:
      if (m_logged) {
        getUserContact(((ContactInfoChangedEvent *)event)->m_simId, getMyStatus());
      }
      break;

    case SimEventBase::event_msg:
      if (m_logged) {
        MessageReceivedEvent * emsg = (MessageReceivedEvent *)event;
        int id = getContactId(emsg->m_simId, true);
        if (Contact * c = getContact(id)) {
          c->m_newMessages++;
          if (!emsg->m_noNotify) {
            SoundEffects::playMsgSound(id, c && c->m_chatFrame && c->m_chatFrame->isFocused() && // chat frame is focused
                                             c->m_chatFrame->hasAutoScroll());                   // Auto Scroll is active
            // if Auto Scroll is not active message will not be shown anyway
          }
        }
        if (id >= 0) emit signalMessageReceived(id, emsg->m_ndx, emsg->m_noNotify);
      }
      break;

    case SimEventBase::event_edit:
      if (m_logged) {
        MessageEditEvent * emsgedit = (MessageEditEvent *)event;
        int id = getContactId(emsgedit->m_simId, true);
        if (id >= 0) {
          emit signalMessageEdited(id, emsgedit->m_ndx);
          if (Contact * c = getContact(id)) {
            SoundEffects::playMsgSound(id, c && c->m_chatFrame && c->m_chatFrame->isFocused() && // chat frame is focused
                                             c->m_chatFrame->hasAutoScroll());                   // Auto Scroll is active
            // if Auto Scroll is not active message will not be shown anyway
          }
        }
      }
      break;

    case SimEventBase::event_sent:
      if (m_logged) {
        MessageSentEvent * emsgsent = (MessageSentEvent *)event;
        int id = getContactId(emsgsent->m_simId, true);
        switch (emsgsent->m_reason) {
          case 'r': SoundEffects::playMsgResentSound(id); break;
          case 'n': SoundEffects::playMsgNotsentSound(id); break;
        }

        if (id >= 0 && getContact(id)) {
          const std::vector<int> & ndxs = *emsgsent->m_ndxs;
          for (unsigned i = ndxs.size(); i--;) {
            emit signalMessageSent(id, ndxs[i], SIM_MSG_DELIVERED);
          }
        }
        delete emsgsent->m_ndxs;
      }
      break;

    case SimEventBase::event_xfer:
      if (m_logged) {
        TransferStateEvent * estate = (TransferStateEvent *)event;
        emitTransferStateChanged(getContactId(estate->m_simId, true), estate->m_xferId, estate->m_type);
      }
      break;

    case SimEventBase::event_file:
      if (m_logged) {
        TransferEvent * efile = (TransferEvent *)event;
        int id = getContactId(efile->m_simId, true);
        if (efile->m_xferId != SIM_XFER_GET_RECEIVED && efile->m_xferId != SIM_XFER_GET_SENT) {
          QStringList list = efile->m_newName.split(' ');
          if (list.size() >= 2) {
            int notification = list.at(0) == "SEND" ? 1 : list.at(0) == "RECV" && list.at(1) == "0" ? 2 : 0;
            bool outgoing = (efile->m_xferId >> 62) & 1;
            if ((m_notifications < 0 ? 0 : m_notifications ? m_notifications : -1) >= notification * !outgoing) {
              if (!outgoing || list.at(0) == "RECV") {
                if (Contact * c = getContact(id)) c->setNotifications(Contact::flag_hasIncomingFile);
              } else if (list.at(0) != "SEND" && list.at(1) != QString::number(SIM_XFER_CANCELLED)) {
                if (Contact * c = getContact(id)) c->setNotifications(Contact::flag_hasIncomingFile);
              }
            }

            if (!outgoing && list.at(0) == "SEND") {
              SoundEffects::playFileReceiveSound(id);
            } else if (list.at(0) == "RECV") {
              if (list.at(1) != "0") {
                SoundEffects::playFileCancelledSound(id);
              } else if (outgoing) {
                SoundEffects::playFileReceivedSound(id);
              } else {
                SoundEffects::playFileSentSound(id);
              }
            } else if (list.at(0) == "REJECT" || list.at(0) == "CANCEL") {
              outgoing = outgoing && list.at(1) == QString::number(SIM_XFER_CANCELLED);
              if (!outgoing || SimParam::get("sound.play.cancelled") & 8) SoundEffects::playFileCancelledSound(id);
            }
          }
        }
        emitTransferChanged(id, efile->m_xferId & ~(simnumber(1) << 62), efile->m_oldName, efile->m_newName);
      }
      break;

    case SimEventBase::event_audio: {
      Contact * c;
      AudioEvent * eaudio = (AudioEvent *)event;
      SimAudioState oldState = eaudio->m_oldAudioState;
      SimAudioState newState = eaudio->m_newAudioState;

      int id = getContactId(eaudio->m_simId, true);
      if (id < 0 && (eaudio->m_simId == CONTACT_ID_KEYGEN || eaudio->m_simId == CONTACT_ID_TEST)) {
        id = getTestContactId();
      }
      if (id >= 0 && (c = getContact(id))) {
        int t;
        //log_note_("ui", "@%020llu status %x, audio %d-%d\n", eaudio->m_simId, c->m_state, oldState, newState);
        if (!m_logged && !c->isTest()) break;
        c->m_state &= ~Contact::flag_hasMissedCall;
        switch (newState) {
          case audio_incoming: // incoming call
            mc_calling = true;
            c->m_state |= Contact::flag_hasMissedCall | Contact::flag_notify;
            c->setCallState(Contact::call_incoming);
            if (oldState == audio_outgoing) SoundEffects::stopOutgoingCallSound(id);
            SoundEffects::playIncomingCallSound(id);
            break;

          case audio_talk: // talking
            mc_calling = false;
            mc_callConnection = state_disconnect;
            mc_callTime = sim_get_tick();
            m_speechProb = -1;
            m_speechTimer.setSingleShot(true);
            t = SimParam::get("ui.call.speech");
            if (t) m_speechTimer.start(SimParam::get("speex.speech.delay") + t);
            c->setCallState(Contact::call_talking);
            if (oldState == audio_incoming) {
              SoundEffects::stopIncomingCallSound(id);
            } else if (oldState == audio_outgoing) {
              SoundEffects::stopOutgoingCallSound(id);
            }
            for (int i = 0; i < int(m_contacts.size()); ++i) { // hang up all calls
              if (Contact * contact = m_contacts[i]) {
                if (contact->isCallState(Contact::call_incoming) || contact->isCallState(Contact::call_outgoing)) {
                  sim_audio_hangup_(contact->m_simId);
                }
              }
            }
            if (!c->isTest()) {
              m_latencyId = eaudio->m_simId;
              mc_callConnection = getConnectionState(id);
              if (mc_callConnection != state_relaying_in && mc_callConnection != state_relaying_out) {
                m_latencyCount = 0;
                m_latencyTimer.start(m_latencyDelay);
                log_debug_("ui", "latency timer start\n");
              }
            }
            m_hangupTimer.stop();
            break;

          case audio_udp: // talking over UDP
            emit signalContactConnectionChanged(id, 'u');
            mc_callConnection = getConnectionState(id);
            break;

          case audio_connect: // connecting
            mc_calling = true;
            c->setCallState(Contact::call_outgoing);
            if (oldState == audio_incoming) SoundEffects::stopIncomingCallSound(id);
            SoundEffects::playOutgoingConnectSound(id);
            break;

          case audio_outgoing: // outgoing call (not yet ringing)
            mc_calling = true;
            c->setCallState(Contact::call_outgoing);
            if (oldState == audio_incoming) SoundEffects::stopIncomingCallSound(id);
            break;

          case audio_hangup:
            mc_calling = false;
            c->setCallState(Contact::call_none);
            if (oldState == audio_talk && eaudio->m_simId != CONTACT_ID_KEYGEN) {
              bool test = eaudio->m_simId == CONTACT_ID_TEST;
              if (test || simnumber(sim_get_tick()) - mc_callTime >= SimParam::get("audio.timeout") + 1000) {
                m_hangupData = c;
                m_hangupTimer.setSingleShot(true);
                m_hangupTimer.start(test ? 0 : SimParam::get("ui.call.hangup"));
              }
            } else if (oldState == audio_incoming) {
              SoundEffects::stopIncomingCallSound(id);
            } else if (oldState == audio_outgoing) {
              SoundEffects::stopOutgoingCallSound(id);
            }
            if (m_latencyDialog && eaudio->m_simId == m_latencyId) m_latencyDialog->reject();
            m_latencyId = 0;
            m_latencyTimer.stop();
            m_latencyClosed = false;
            log_debug_("ui", "latency timer stop\n");
            if (m_speechDialog) m_speechDialog->reject();
            m_speechTimer.stop();
            m_speechProb = -1;
            emit signalContactConnectionChanged(id, 'a');
            break;

          case audio_ringing: // outgoing call now ringing
            mc_calling = true;
            c->setCallState(Contact::call_outgoing);
            SoundEffects::stopOutgoingConnectSound(id);
            if (oldState == audio_incoming) SoundEffects::stopIncomingCallSound(id);
            SoundEffects::playOutgoingCallSound(id);
            break;

          case audio_missed:
            // do not show missed call if currently on focus
            if (!c->m_chatFrame || !c->m_chatFrame->isFocused()) {
              c->m_state |= Contact::flag_hasMissedCall;
              if (oldState == audio_none) c->m_state |= Contact::flag_notify;
            }
            break;

          case audio_abort:
            mc_calling = false;
            SoundEffects::playHangupSound(id);
            break;

          case audio_busy:
            mc_calling = false;
            SoundEffects::playBusySound(id);
            break;

          case audio_failed:
            mc_calling = false;
            SoundEffects::playFailedSound(id);
            break;

          case audio_disconnect:
            mc_calling = false;
            SoundEffects::playDisconnectSound(id);
            break;

          case audio_none:
            break;
        }

        emit signalContactAudioChanged(id, newState);
      }
    } break;

    case SimEventBase::event_speech: {
      SpeechEvent * espeech = (SpeechEvent *)event;
      int id = espeech->m_simId == CONTACT_ID_KEYGEN ? -1 : getContactId(espeech->m_simId, true);
      if (espeech->m_prob > m_speechProb) m_speechProb = espeech->m_prob;
      emit signalSpeech(id, espeech->m_err, espeech->m_prob, espeech->m_percent);
    } break;

    case SimEventBase::event_keygen: {
      KeygenEvent * ekeygen = (KeygenEvent *)event;
      //log_note_("ui", "SimCore::customEvent keygen time: %llu, approx %lld\n", sim_get_tick(), ekeygen->m_time2gen);
      if (ekeygen->m_simId) SoundEffects::playKeygenSound(-1); // keygen done

      if (!m_keygenDialog) m_keygenDialog = new KeyGen;
      emit signalKeygen(ekeygen->m_address, ekeygen->m_time2gen);

      if (!ekeygen->m_time2gen && !mc_keygenPass.isNull()) {
        int simres = ekeygen->m_err;
        if (simres == SIM_OK) simres = startThread(QString(), SimThread::thread_login);
        if (simres == SIM_OK && !mc_startupUser.isNull()) {
          QString qs = Login::translateKey(mc_keygenPass);
          simres = sim_key_set_password_(qs.toUtf8().data(), SIM_PASSWORD_BIT_OVERWRITE);
          if (simres != SIM_OK && sim_exit_user_() != SIM_OK) _exit(EXIT_FAILED_PASSWORD);
        }
        mc_keygenPass.clear();
        if (simres == SIM_OK) {
          loginFinish();
        } else {
          loginError(simres, "", false);
        }
      }
    } break;

    case SimEventBase::event_net: {
      NetEvent * enet = (NetEvent *)event;
      switch (enet->m_code) {
        case 't':
        case 'c':
        case 'd': {
          bool traversed = enet->m_code == 't' && enet->m_err != SIM_NAT_TRAVERSE_ATTEMPT;
          int id = getContactId(enet->m_simId, true);
          Contact * c = id < 0 ? 0 : getContact(id);
          if (c && enet->m_code != 'd') c->setTraversed(traversed);
          emit signalContactConnectionChanged(id, enet->m_code);
          if (sim_audio_check_talking_() == enet->m_simId) mc_callConnection = getConnectionState(id);
          if (c && traversed && enet->m_simId == m_latencyId && !m_latencyTimer.isActive()) {
            m_latencyCount = 0;
            m_latencyTimer.start(m_latencyDelay);
            log_debug_("ui", "latency timer start (traversed)\n");
          }
        } break;

        case 'H':
          g_DHTstatus = enet->m_err;
          if (Contact * c = getContact(getTestContactId())) emit signalContactConnectionChanged(c->m_id, 'h');
          break;

        case 'h':
          if (m_logged) {
            if (!enet->m_err) {
              Contacts::get()->showStatus(tr("Connected to the DHT network."), ' ');
              SoundEffects::playUpSound(g_DHTinit);
            } else {
              Contacts::get()->showStatus(tr("Disconnected from the DHT network."), 'e');
              SoundEffects::playDownSound(false);
            }
            g_DHTinit = false;
            getMyContact();
          }
          break;

        case 'L':
          if (m_logged) {
            if (enet->m_err != SIM_OK) {
              Contacts::get()->showStatus(enet->m_name + ".", enet->m_err == SIM_LIMIT_DHT_ENABLE ? ' ' : 'e');
            } else {
              Contacts::get()->showStatus(tr("Incoming connections have been enabled again."), ' ');
            }
          }
          break;

        case 'q':
          if (m_logged && !m_latencyClosed && enet->m_simId == m_latencyId && m_latencyTimer.isActive()) {
            checkAudioLatency(enet->m_simId);
          }
          break;

        case 'o':
          if (m_hangupTimer.isActive()) {
            m_hangupTimer.stop();
            onHangupTimeout();
          }
          break;
      }
    } break;

    case SimEventBase::event_device: {
      InvalidDeviceEvent * einvalid = (InvalidDeviceEvent *)event;
      bool outputOK = einvalid->m_output.isNull();
      bool inputOK = einvalid->m_input.isNull();
      bool ringOK = einvalid->m_ring.isNull();
      bool devicesOK;
      bool invalidOutput;
      bool invalidInput;
      bool invalidRing;

      Settings::getInvalidDevices(&invalidOutput, &invalidInput, &invalidRing);
      Settings::setInvalidDevices(!outputOK, !inputOK, !ringOK);
      devicesOK = (outputOK || invalidOutput) && (inputOK || invalidInput) && (ringOK || invalidRing);

      if (mc_hidden == 'h' && !Settings::isActive()) {
        mc_hasAudioChanged = !devicesOK;
      } else if (!devicesOK || Settings::isActive()) {
        mc_hasAudioChanged = false; // will be shown now
        emit signalInvalidAudioDevice(devicesOK);
      }
    } break;

    case SimEventBase::event_error: {
      ErrorEvent * eerror = (ErrorEvent *)event;
      int id = !eerror->m_simId ? -1 : getContactId(eerror->m_simId, true);
      Contact * c = id < 0 ? 0 : getContact(id);
      QString error;
      QString simerr;

      switch (eerror->m_code) {
        case 'i':
          simerr = eerror->m_name;
#ifdef _WIN32
          if (eerror->m_err != SIM_API_INSTALL_CANCELLED) {
            if (eerror->m_err != SIM_API_INSTALL_FAILED && eerror->m_err != WAIT_TIMEOUT) {
              simerr.append("\n\n").append(tr("Make sure the administrator has read access"
                                              " to the directory where Simphone is installed."));
            }
          }
#endif
          if (mc_hidden != 'h') {
            if (execMessageBox(true, tr("Installation not successful (%1)").arg(eerror->m_err), simerr,
                               tr("Simphone works fine through my firewall.\n"
                                  "Don't bug me with this error message ever again."))) {
              SimParam::set("system.install", 0, false);
            }
          }
          break;

        case 'L':
          if (eerror->m_err == SIM_SERVER_PORT) {
            error = tr("The incoming port number you have set is not available. Either quit the application,"
                       " which is using this port or change the port number from the \"Network\" tab of \"Settings\".");
          } else {
            error = tr("The \"localhost\" network interface is not available.");
          }
          error.append("\n\n");
          error.append(tr("Simphone has now started in offline mode. Fix the above error and restart Simphone."));
          if (mc_hidden != 'h') execMessageBox(true, eerror->m_name, error);
          break;

        case 'S':
          mc_hasSocksError = mc_hidden != 'h' ? 0 : eerror->m_err ? eerror->m_err : SIM_SOCKET_CONNECT;
          if (!mc_hasSocksError) execMessageBox(true, getErrorBuffer(SIM_SOCKET_CONNECT), eerror->m_name);
          break;

        case 'u':
          if (mc_hidden != 'h') {
            QMessageBox::information(Contacts::get(), tr("STATUS OFF"),
                                     tr("You have been logged off, because of multiple login.\n\nChange your status"
                                        " back to \"online\" manually as soon as you are back at the computer."));
          } else {
            g_fileErrorsReceived.append(new ErrorEvent(eerror->m_simId, eerror->m_code, eerror->m_err, eerror->m_name));
          }
          break;

        case 'a':
          simerr = eerror->m_name;
          if (eerror->m_err != SIM_CALL_NO_ECHO_CANCELLER) {
            bool test = eerror->m_simId == CONTACT_ID_TEST || eerror->m_simId == CONTACT_ID_KEYGEN;
            if (test) {
              error = tr("Your audio test seems to have been not successful (%1)").arg(eerror->m_err);
            } else {
              error = tr("Your audio call seems to have been not successful (%1)").arg(eerror->m_err);
            }
            if (mc_hidden != 'h' && (test || c)) execMessageBox(true, error, simerr);
          } else if (c) {
            QString qs = tr("You have requested echo cancellation, but <b>%1</b>'s audio settings do not allow it."
                            " You may hear an echo of your own voice, because of this.");
            error = tr("Your audio call has reported a warning (%1)").arg(eerror->m_err);
            if (mc_hidden != 'h') {
              execMessageBox(true, error, simerr.append("<br/><br/>").append(qs.arg(c->m_nick.toHtmlEscaped())));
            }
          }
          break;

        case 'x':
          if (c) {
            QString qs;
            error = eerror->m_name;
            if (eerror->m_err == SIM_FILE_NO_SPACE) {
              if (!g_xferErrorReceived && SimParam::get("ui.xfer.nospace")) {
                simerr = tr("A file sent to you by <b>%1</b> is larger than the currently available space."
                            " You should either cancel the file or free some disk space, so the file may be received.");
                simerr = simerr.arg(c->m_nick.toHtmlEscaped());
              }
              g_xferErrorReceived = true;
              qs = tr("I manage my free space correctly. Do not show this error message ever again.");
            } else if (eerror->m_err == SIM_XFER_NO_NAME) {
              simerr = tr("A file sent to you by <b>%1</b> cannot be received,"
                          " because the maximum number of files has already been downloaded."
                          " You should either increase the allowed number of received files per contact or move some"
                          " of them out of the download directory, so you can receive more files from <b>%1</b>.");
              simerr = simerr.arg(c->m_nick.toHtmlEscaped());
            }
            if (!simerr.isEmpty()) {
              mc_hasXferError = mc_hidden != 'h' ? 0 : eerror->m_err;
              mc_hasXferErrorId = c->m_simId;
              if (!mc_hasXferError && execMessageBox(true, error, simerr, qs)) {
                SimParam::set("ui.xfer.nospace", 0, false);
              }
            }
            g_xferErrorReceived = false;
          }
          break;

        case 'p':
        case 'k':
          error = tr("SIMPHONE SECURITY ALERT");
          if (c) error.append(" (").append(c->m_nick).append(")");
          if (!c) c = getContact(getTestContactId()); // global crypto errors go to the console contact data
          if (mc_hidden == 'h') {
            c->setCryptoError(eerror->m_err, true);
            if (eerror->m_err != SIM_CRYPT_BAD_KEY && eerror->m_err != SIM_MSG_OVERFLOW) {
              if (++mc_hasProtocolError < 0) mc_hasProtocolError = 1;
            }
          } else {
            c->clearCryptoError(eerror->m_err, true);
            if (eerror->m_err == SIM_CRYPT_BAD_KEY) {
              SoundEffects::playFakeKeySound(id);
            } else if (eerror->m_err != SIM_MSG_OVERFLOW) {
              SoundEffects::playProtocolSound(id, 1);
            }
            if (!c->isCryptoError(eerror->m_err, false)) {
              c->setCryptoError(eerror->m_err, false);
              if (eerror->m_err == SIM_MSG_OVERFLOW) {
                if (c) {
                  simerr = tr("Your chat with <b>%1</b> has caused a"
                              " <span style=\" color:#ff0000;\"><b>MEMORY OVERFLOW</b></span>.");
                  simerr = simerr.arg(c->m_nick.toHtmlEscaped());
                } else {
                  simerr = tr("Your console log has caused a"
                              " <span style=\" color:#ff0000;\"><b>MEMORY OVERFLOW</b></span>.");
                }
                simerr.append(" ").append(tr("That window has been disabled, until you free some memory."));
                simerr.append("<br/><br/>");
                simerr.append(tr("To do so, right-click the chat and choose <b>Show Less</b> or restart Simphone."));
                if (!c->isMe()) {
                  QString qs = tr("If you have recently sent many messages, you first have to:<ul>"
                                  "<li>Wait for them to be received by your contact <b>OR...:</b></li>"
                                  "<li>Select non-received messages, then right-click and choose"
                                  " <b>Delete Messages</b> to destroy them.</li></ul>Communication with"
                                  " <b>%1</b> will be enabled automatically, when there is enough memory available.");
                  simerr.append("<br/><br/>").append(qs.arg(c->m_nick.toHtmlEscaped()));
                }
              } else {
                simerr = getErrorBuffer(eerror->m_err);
              }
              execMessageBox(true, error, simerr);
              c->clearCryptoError(eerror->m_err, false);
            }
          }
          break;

        case 'm':
        case 'h':
          if (c) {
            if (eerror->m_code == 'h') {
              c->m_badCryptoName = eerror->m_name;
              if (!c->m_badCryptoName.isEmpty()) {
                error = tr("%1 uses undesired cipher \"%2\".").arg(c->m_nick, c->m_badCryptoName);
                Contacts::get()->showStatus(error, 'w');
              }
            } else {
              c->m_badSystemName = eerror->m_name;
              if (!c->m_badSystemName.isEmpty() && !c->isMe()) {
                error = tr("%1 uses insecure system \"%2\".").arg(c->m_nick, c->m_badSystemName);
                Contacts::get()->showStatus(error, 'e');
              }
            }
          }
          break;

        default:
          error = convertErrorToString(eerror->m_code, c);
          if (!error.isEmpty()) {
            if (mc_hidden != 'h') {
              if (c) error = error.arg(c->m_nick);
              simerr = eerror->m_name;
              if (eerror->m_code != 'l' && eerror->m_code != 'W') {
                simerr.append("\n\n");
                if (eerror->m_code != 'c' && eerror->m_code != 'd' && eerror->m_code != 'r') {
                  simerr.append(tr("If you are out of disk space, free some before you quit Simphone,"
                                   " in order to avoid (further) data loss."));
                } else {
                  simerr.append(tr("If you have no write access to your user directory, fix the problem"
                                   " in order to avoid (further) data loss."));
                }
                if (getMyStatus() == SIM_STATUS_OFF) {
                  char buffer[64];
                  simerr.append(" ").append(tr("You should change your status back to \"online\" afterwards."));
                  if (sim_convert_time_to_string(time(0), buffer)) simerr.append("\n\n").append(buffer);
                }
              }
              execMessageBox(true, error.arg(eerror->m_err), simerr);
            } else {
              SimEventBase * ev = new ErrorEvent(eerror->m_simId, eerror->m_code, eerror->m_err, eerror->m_name);
              g_fileErrorsReceived.append(ev);
            }
          }
      }
    } break;

    case SimEventBase::event_log:
      Logs::newLog();

#ifndef _DEBUG
      if (mc_hidden == 'h' && !SimParam::get("ui.main.popuphide")) break;
#endif
      if (((LogEvent *)event)->m_level == SIM_LOG_ERROR) {
        if (!m_logged) {
          Contact * c = getContact(getTestContactId());
          if (!c || !c->m_chatFrame || !c->m_chatFrame->isVisible()) {
            ConsoleDialog console(0, true);
            console.exec();
          }
        } else {
          Contacts::get()->activateContactChat(getTestContactId(), 2);
        }
      }
      break;

    default:
      log_error_("ui", "unexpected event type %d\n", int(event->type()));
  }
}

bool SimCore::execMessageBox(QMessageBox::Icon icon, const char * title, const QString & message,
                             const QString & checkBox)
{
  return qtfix::execMessageBox(icon, title, message, getParent(Contacts::get()), checkBox);
}

bool SimCore::execMessageBox(QCheckBox * checkBox, const QString & title, const QString & message, bool yes)
{
  return qtfix::execMessageBox(checkBox, title, message, getParent(Contacts::get()), yes);
}

bool SimCore::execMessageBox(bool critical, const QString & title, const QString & message,
                             const QString & checkBox)
{
  return qtfix::execMessageBox(critical, title, message, getParent(Contacts::get()), checkBox);
}

bool SimCore::checkProcess(quint64 pid, bool terminate)
{
  bool ok = true;
#ifdef _WIN32
  HANDLE handle = pid ? OpenProcess(terminate ? PROCESS_TERMINATE : SYNCHRONIZE, FALSE, pid) : 0;

  if (!handle) {
    errno = pid ? GetLastError() : 0;
    return false;
  }
  SetLastError(0);
  if (terminate) {
    ok = TerminateProcess(handle, 666) != 0;
  } else if (WaitForSingleObject(handle, 0) == WAIT_OBJECT_0) {
    ok = false;
  }
  errno = GetLastError();
  CloseHandle(handle);
#else
  errno = 0;
  if (!pid || (kill(pid, terminate ? SIGKILL : 0) && errno && errno != EPERM)) ok = false;
#endif
  return ok;
}

bool SimCore::killProcess(quint64 pid, int timeout)
{
  log_debug_("ui", "%s %llu\n", __FUNCTION__, pid);
  if (!checkProcess(pid, true)) return false;
  errno = 0;
  for (timeout = (timeout + 99) / 100; timeout; timeout--) {
    if (!checkProcess(pid, false)) return true;
#ifdef _WIN32
    Sleep(100);
#else
    usleep(100000);
#endif
  }
  return false;
}

int SimCore::execProcess()
{
  QString qs;

  if (!g_exeName.isEmpty()) { // shut down single application IPC first. no Qt calls possible afterwards
    delete qApp;
    //qApp = 0;
#ifdef _WIN32
    STARTUPINFOW si;
    PROCESS_INFORMATION pi;

    GetStartupInfoW(&si);
    qs = QString((QChar *)GetCommandLineW()).append(" -noinstall");
    if (CreateProcessW((wchar_t *)(QString((char *)g_exeName.data()).utf16()),
                       (wchar_t *)(qs.utf16()), 0, 0, FALSE, 0, 0, 0, &si, &pi)) return EXIT_EXEC_SUCCEEDED;
#else
    execv(g_exeName.data(), qtfix::getArguments());
#endif
  }
  return EXIT_EXEC_FAILED;
}

void SimCore::registerHotKey(int id, int hotKey)
{
#ifdef _WIN32
  if (!RegisterHotKey(HWND(m_hwnd), id, hotKey >> 8, hotKey & 255)) {
    log_warn_("ui", "failed to register hotkey%d 0x%X (error %d)\n", id, hotKey, GetLastError());
  }
#endif
#ifdef __APPLE__
  EventHotKeyID key = { 0, unsigned(id) };
  OSStatus status = RegisterEventHotKey(hotKey & 0x7F, hotKey & 0x1F80, key, GetApplicationEventTarget(), 0,
                                        (EventHotKeyRef *)&m_hotKey[id - 1]);
  if (status) log_warn_("ui", "failed to register hotkey%d 0x%X (error %d)\n", id, hotKey, status);
#else
  m_hotKey[id - 1] = (void *)long(hotKey);
#endif
#ifdef HAVE_LIBXCB
  if (!m_hotKeyThread) m_hotKeyThread = new HotKeyThread;
  m_hotKeyThread->init(id, hotKey);
  m_hotKeyThread->start();
#endif
}

void SimCore::unregisterHotKey(int id)
{
#ifdef _WIN32
  if (m_hwnd && m_hotKey[id - 1] && !UnregisterHotKey(HWND(m_hwnd), id)) {
    log_warn_("ui", "failed to unregister hotkey%d (error %d)\n", id, GetLastError());
  }
#endif
#ifdef __APPLE__
  if (m_hotKey[id - 1]) {
    OSStatus status = UnregisterEventHotKey((EventHotKeyRef)m_hotKey[id - 1]);
    if (status) log_warn_("ui", "failed to unregister hotkey%d (error %d)\n", id, status);
  }
#endif
#ifdef HAVE_LIBXCB
  if (m_hotKeyThread && m_hotKey[id - 1]) m_hotKeyThread->init(-id, int(long(m_hotKey[id - 1])));
#endif
  m_hotKey[id - 1] = 0;
}

void SimCore::readSettings()
{
  int hidekey = SimParam::get("ui.main.hidekey");
  int showkey = SimParam::get("ui.main.showkey");

  unregisterHotKey(1);
  if (hidekey > 0) registerHotKey(1, hidekey);
  unregisterHotKey(2);
  if (showkey > 0) registerHotKey(2, showkey);

  m_notifications = SimParam::get("ui.xfer.notifications");
}

void SimCore::readStyleSheet(const QString & fileName)
{
  QString styleSheet;
  if (!fileName.isEmpty()) {
    QFile file(fileName);
    if (file.open(QFile::ReadOnly)) {
      QTextStream stream(&file);
      styleSheet = stream.readAll();
    }
  }
  for (int i = styleSheet.indexOf("/*"); i >= 0; i = styleSheet.indexOf("/*", i + 1)) {
    int j = styleSheet.indexOf("*/", i);
    QString comment = styleSheet.mid(i + 2, j - i - 2).trimmed();
    if (comment.startsWith("simphone:")) {
      QStringList list = comment.mid(9).split("=");
      if (list.size() == 2) {
        QString param = list.at(0).trimmed();
        QStringList group = param.split(".");
        if (group.size() == 2 && (group.at(0) == "chat" || group.at(0) == "console" || group.at(0) == "color")) {
          SimParam::set(("ui." + param).toUtf8().data(), list.at(1).trimmed().toUtf8().data(), true);
        }
      }
    }
    if (j < 0) break;
    i = j + 2;
  }
}

bool SimCore::loginUser(bool createKey)
{
  if (mc_hidden == 'h') return false;
  do {
    if (createKey) {
      bool newLogin = false;

      while (!newLogin) {
        unsigned genType;
        int genBits;
        int genRslt;

        {
          Generate genDialog;
          genRslt = genDialog.exec();
          genType = genDialog.getType();
          genBits = genDialog.getBits();
        }

        switch (genRslt) {
          case -1: // Alt+Q
          case QDialog::Rejected:
            return false;

          case QDialog::Accepted:
            if (!genBits || !genType) {
              return false;
            } else {
              int seedRslt = QDialog::Rejected;
              bool seed = SimParam::get("ui.login.countries") < 2;
              QString seedStr0;
              QString seedStr1;
              {
                SeedGen seedGen(0, new KeygenEvent(0, SIM_OK, -2, "init"), genBits, genType);
                seedRslt = seedGen.exec();
                seedStr0 = seedGen.getSeed(seed);
                seedStr1 = seedGen.getSeed(!seed);
              }
              if (seedRslt < 0) return false;

              if (genBits < 0 && !seedStr0.isNull()) { // seedless key generation
                mc_lastPass.clear();
                mc_keygenPass = "";
                return true;
              }

              if (seedRslt == QDialog::Accepted && !seedStr0.isEmpty()) {
                int rslt;
                {
                  ShowSeed showSeed(0, seedStr0, seedStr1);
                  rslt = showSeed.exec();
                  if (rslt == QDialog::Rejected || rslt < 0) return false;
                }

                // new login (new seed has been created)
                if (rslt != QDialog::Accepted) {
                  mc_lastPass = seedStr0;
                  return loginStart(seedStr0.toUtf8().data(), "");
                }
                mc_lastPass.clear();
                newLogin = true;
              }
            }
            break;

          default:
            newLogin = true; // not rejected, but not accepted for key generation
        }
      }
    }

    do {
      Login login(0, mc_lastPass.toUtf8().data());
      int loginRslt = login.exec();
      QString loginPass = login.getKey();

      mc_lastPass = loginPass;
      login.close(); // just in case of
      if (loginRslt == QDialog::Accepted) {
        return loginStart(loginPass.toUtf8().data(), loginPass);
      }
      if (loginRslt == QDialog::Rejected) break;
      if (loginRslt < 0) return false;

      int simres = startThread("", SimThread::thread_login);

      mc_lastPass.clear();
      if (simres == SIM_OK) {
        loginFinish();
        return true;
      }

      createKey = true;
      if ((mc_startupUser.isNull() && simres != SIM_KEY_BAD_KEY) || simres == SIM_KEY_NO_KEY) break;
      if (!mc_startupUser.isEmpty() || simres == SIM_KEY_BAD_KEY) {
        loginError(simres, "", true);
        return true;
      }

      QString qs = tr("<b>This computer already has a Simphone user.</b><br/><br/>If this is not"
                      " your computer, push <b>%1</b> and quit Simphone now.<br/><br/>If you have"
                      " forgotten or lost access to your secret key, push <b>%2</b> to rename your user"
                      " directory, so that you can start over and create a new identity.<br/><br/>"
                      "If you are an advanced user, who would like to use multiple identities, quit"
                      " Simphone and use the <b>-user</b> command-line argument with a user directory of"
                      " your choice.<br/><br/>Do you want to rename your user directory?");
      qs = qs.arg(qApp->translate("QShortcut", "No"), qApp->translate("QShortcut", "Yes"));
      if (QMessageBox::question(0, tr("Remove secret key"), qs,
                                QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::Yes) {
        qs = QString();
        simres = sim_exit_();
        if (simres == SIM_OK) {
          simtype path = sim_init_path_(mc_startupUser.toUtf8().data(), false);
          qs = sim_get_pointer(path);
          sim_string_free(path);
        }
        if (simres == SIM_OK && !qs.isEmpty()) simres = sim_init_move(qs.toUtf8().data(), "moved_user_");
        if (simres != SIM_OK || qs.isEmpty()) {
          execMessageBox(true, tr("Rename not successful (%1)").arg(simres), getError(simres));
        }

        _exit(execProcess());
        return false;
      }

      createKey = false;
    } while (!createKey);
  } while (createKey);
  return false;
}

bool SimCore::loginStart(const char * password, const QString & keygenPassword)
{
  if (mc_startupOffline || mc_startupSocks) SimParam::set("client.status", "off", true);

  int simres = *password ? SIM_NO_ERROR : startThread(QString(), SimThread::thread_login);
  if (simres != SIM_OK) simres = startThread(password, SimThread::thread_login);

  if (mc_autolog && !*password && simres == SIM_KEY_NO_KEY) { // we have no saved key
    simtype versions = sim_list_versions();
    QString version = sim_table_get_pointer(versions, SIM_VERSION_BAD);
    sim_list_free(versions);

    mc_autolog = false;

    if (!version.isEmpty() && mc_hidden != 'h') {
      QString message =
        tr("<span style=\" color:#ff0000;\"><br/><h1><b><center>ATTENTION ! ! !</center></b></h1><br/></span>"
           "<h3><b><center>Simphone has detected an OS on your computer, which used or uses widely discussed,"
           " not always obvious functions.</center></b></h3><br/>This <b>%1</b> operating system was or is likely to"
           " collect keyboard entries as well as sound snippets and further data. This personal data of yours might"
           " still be sent to internet addresses neither explicitly shared with you, nor that you have much influence"
           " to safely avoid any of these actions. You have accepted this by confirming their license agreement;"
           " many users lack the in-depth technical knowledge required to prevent such operations.<br/><br/>"
           "<span style=\" color:#ff0000;\"><b>Even if you configure or configured your system to disable these"
           " features, they may still be or become operational again, despite of different claims from the OS."
           "</b></span><br/><br/>Concerning the protection of privacy and anonymity, it may be expressed that an OS"
           " like Linux fulfilled these requirements in the past and thus is likely to do so still. Some variants"
           " even offer a bootable stick with writing capabilities, which could be used with this computer as well."
           " If you ignore this warning and proceed to generate a new key, unauthorized parties might gain access"
           " to your communications and possibly obtain your secret key as well, so they can impersonate you or"
           " revoke your Simphone address.<br/><br/><b>Are you <span style=\" color:#ff0000;\">absolutely</span>"
           " sure, that you have avoided all security risks up to a level, you consider to be safe enough for you"
           " and your communication partners?</b><br/><br/>");
      QInputDialog mbox(0);
      mbox.setWindowTitle(tr("SIMPHONE SECURITY"));
      mbox.setLabelText(message.arg(version.toHtmlEscaped()));
      QLabel * label = (QLabel *)qtfix::findChild(&mbox, "QLabel");
      if (label) {
        label->setWordWrap(true);
        label->setAlignment(Qt::AlignVCenter | Qt::AlignLeft);
      }
      mbox.setOkButtonText(qApp->translate("QPlatformTheme", "&Yes"));
      mbox.setCancelButtonText(qApp->translate("QPlatformTheme", "&No"));
      mbox.show();
      QDialogButtonBox * box = (QDialogButtonBox *)qtfix::findChild(&mbox, "QDialogButtonBox");
      if (box) {
        QPushButton * button = box->button(QDialogButtonBox::Cancel);
        if (button) {
          button->setDefault(true);
          button->setFocus(Qt::OtherFocusReason);
        }
      }
      QLineEdit * lineEdit = (QLineEdit *)qtfix::findChild(&mbox, "QLineEdit");
      if (lineEdit) lineEdit->hide();
      if (mbox.exec() == QMessageBox::NoButton) {
        QMessageBox::information(0, tr("SIMPHONE SECURITY"),
                                 tr("<br/>Simphone will now exit."
                                    " Thank you for your time and we hope to have helped enlighten the theme around"
                                    " <b>avoidable</b> security breaches concerning all internet users. Goodbye."));
        _exit(0);
      }
    }
    return loginUser(true);
  }
  //log_note_("ui", "SimCore::loginStart %s, time %llu \n", password, sim_get_tick());

  if (mc_hidden == 'h') password = "";
  if (*password) mc_autolog = false; // show error message if -pass parameter given
  if (simres == SIM_OK) {
    loginFinish();
  } else {
    if (loginError(simres, password, !*password && !mc_autolog)) mc_keygenPass = keygenPassword;
  }

  return true;
}

void SimCore::loginFinish()
{
  Contacts * contacts = Contacts::get();

  mc_autolog = false;
  Settings::setAutostart(SimParam::get("ui.main.autostart"), true);

  if (SimParam::get("ui.console.unload") < 0) ChatFrames::getFrame(getContact(getTestContactId()));

  m_latencyFilter = SimParam::get("ui.call.filter");
  m_latencyDelay = SimParam::get("ui.call.delay") * 1000;
  m_logged = true;

  m_hwnd = contacts->winId();
  readSettings();

  mc_lastPass.clear();
  getUserContacts();
  readStyleSheet(qtfix::getStyleSheetName());
  contacts->readSettings();

  int status = getMyStatus();
  if (mc_startupSocks && status == SIM_STATUS_OFF) {
    int simres = SIM_SOCKET_CONNECT;
    if (SimParam::get("net.tor.port") > 0) {
      simres = contacts->setMyStatus(mc_startupOffline ? SIM_STATUS_OFF : isHiding() ? SIM_STATUS_HIDE : SIM_STATUS_ON);
    }
    if (mc_hidden != 'h' && simres != SIM_OK) {
      QString qs = mc_startupOffline ? tr("offline") : tr("online");
      QString error = tr("Setting status to %1 not successful (%2)").arg(QLocale().quoteString(qs)).arg(simres);
      execMessageBox(false, error, SimCore::getError(simres));
    } else if (simres == SIM_SOCKET_CONNECT) {
      QString error = SimCore::getError(simres);
      g_socksErrorReceived = true;
      QCoreApplication::postEvent(SimCore::get(), new ErrorEvent(0, 'S', SIM_CLIENT_CANCELLED, error));
    }
  } else {
    if (status == SIM_STATUS_HIDE) status = SIM_STATUS_ON;
    if (status == SIM_STATUS_ON && isHiding()) status = SIM_STATUS_HIDE;
    contacts->setMyStatus(status);
  }
  g_DHTinit = getMyStatus() != SIM_STATUS_OFF;
  mc_startupOffline = false;

  if (Contact * contact = getContact()) {
    if (contact->m_nick.isEmpty() || contact->getDefaultNick() == contact->m_nick) {
      if (!m_keygenDialog) m_keygenDialog = new KeyGen;
      m_keygenDialog->showNickEdit();
      m_keygenDialog->restorePosition();
      m_keygenDialog->show();
      setLoginDialog(m_keygenDialog);
      return;
    }
    if (m_keygenDialog) {
      qtfix::hideMinimizedWindow(m_keygenDialog);
      setLoginDialog(0);
    }
  }

  showContacts();
}

void SimCore::showContacts()
{
  Contacts * contacts = Contacts::get();

#ifdef DONOT_DEFINE
  QListIterator<QWidget *> iterator(QApplication::topLevelWidgets());
  while (iterator.hasNext()) {
    QWidget * widget = iterator.next();
    if (!widget->isHidden() && widget->isModal()) {
      mc_startupMinimized = false;
      break;
    }
  }
#endif

  if (mc_startupMinimized) {
    mc_startupMinimized = false;
#ifdef _WIN32 // fix restoreGeometry
    contacts->showMinimized();
    if (!contacts->getStayInTaskbar()) {
      qtfix::hideMinimizedWindow(contacts);
      contacts->doHide();
    }
#else
    if (contacts->getStayInTaskbar()) qtfix::showMinimizedWindow(contacts, SimParam::get("ui.main.sleep"));
#endif
  } else {
    contacts->show();
  }

  if (mc_startupUser.isNull()) Transfers::changeDirectory(true);
}

static bool removeFile(const QString & fileName)
{
  QString newName = fileName;
  QFile::remove(newName.append(".removed"));
  return QFile::rename(fileName, newName) ? true : QFile::remove(fileName);
}

int SimCore::showLoginError(int simres)
{
  QString cs;
  QString simerr = getErrorBuffer(simres);

  if (simres == SIM_KEY_BAD_KEY) {
    cs = tr("Your private key failed to pass login validation tests. This is probably a hardware or a"
            " kernel problem, and other programs are also likely to crash soon. You should shut down the"
            " system immediately and reboot after having repaired the broken hardware or system software.");
    if (!mc_startupUser.isNull()) {
      int validate = 3;
      if (SimParam::get("crypto.validate") >= SimParam::getDefault("crypto.validate", 2, &validate)) {
        cs.append("<br/><br/>");
        cs.append(tr("If this kind of error never repeats with Simphone or with any other applications, and"
                     " unless your key file has been intentionally tampered with, it is possible, even though"
                     " extremely unlikely that your RSA key is insecure. <b><span style=\" color:#ff0000;\">"
                     "Should that prove to be the case, you would be strongly advised to revoke your key and"
                     " create a new Simphone identity.</span> You will not be warned about this again.</b>"));
      }
      cs.append("<br/><br/>");
      cs.append(tr("For your safety, your key validation level has been automatically increased to"
                   " the highest one available; login may be slower from now on. Simphone will now exit."));
      SimParam::set("crypto.validate", validate, false);
      SoundEffects::playProtocolSound(-1, 0);
    }
    QMessageBox::critical(0, tr("Simphone error"), simerr.prepend("<b>").append("</b><br/><br/>").append(cs));
    return -2;
  }

  if (simerr.indexOf('[') < 0 || simerr.lastIndexOf(']') < 0) return 0; // ok
  if (simres == EACCES) {
    execMessageBox(true, tr("Login not successful (%1)").arg(simres), simerr);
    if (Contacts::get()) {
      Contacts::get()->doQuit();
    } else {
      qApp->quit();
    }
    return -1; // permission denied
  }
  switch (simres) {
    case SIM_FILE_ERROR: cs = tr("Remove corrupted file"); break;
    case SIM_FILE_END: cs = tr("Remove truncated file"); break;
    default: return 0;
  }

  simerr = simerr.mid(simerr.indexOf('[') + 1);
  simerr = simerr.left(simerr.lastIndexOf(']'));
  if (!QFileInfo(simerr).isRelative()) return 0;

  QString qs = tr("Your computer or Simphone may have suffered a crash. The <b>\"%1\"</b> file prevents you from"
                  " logging in.<br/><br/>Push <b>%2</b> to remove the damaged file and try to login again."
                  " You may have to do so multiple times, in case there are multiple problems.<br/><br/>If you push"
                  " <b>%3</b>, Simphone is going to exit immediately without logging you in or touching your files."
                  "<br/><br/>Do you want to permanently remove the <b>\"%1\"</b> file now?");
  qs = qs.arg(simerr, qApp->translate("QShortcut", "Yes"), qApp->translate("QShortcut", "No"));
  if (QMessageBox::question(0, cs, qs, QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::Yes) {
    simtype path = sim_init_path_(mc_startupUser.toUtf8().data(), false);

    cs = sim_get_pointer(path);
    sim_string_free(path);
    if (!cs.isEmpty()) {
      QFile file1(cs.append("/").append(simerr));
      QFile file2(cs.append(SIM_FILE_BACKUP));

      if (removeFile(file1.fileName()) | removeFile(file2.fileName())) return 1; // successfully removed the bad file
      execMessageBox(true, cs, tr("The file could not be removed."));
    }
  }

  return -1; // refused or failed to remove the bad file
}

bool SimCore::loginError(int simres, const char * password, bool prompt)
{
  bool nok = simres == SIM_FILE_ERROR || simres == SIM_FILE_END;
  bool autologin = mc_autolog;
  mc_autolog = false;

  if (mc_hidden != 'h' && simres != SIM_OK && (simres != SIM_FILE_BAD_KEY || *password || !autologin)) {
    if (m_keygenDialog) {
      qtfix::hideMinimizedWindow(m_keygenDialog);
      setLoginDialog(0);
    }

  retry:
    int ok = showLoginError(simres);

    if (ok) {
      if (ok == 1) {
        QString qs = Login::translateKey(password);
        simres = qs == password || sim_init_user_(qs.toUtf8().data()) != SIM_OK ? sim_init_user_(password) : SIM_OK;
        if (simres != SIM_OK) goto retry;
        loginFinish();
        return false;
      }
      if (nok) simres = SIM_KEY_BAD_KEY;
    } else {
      if (*password) {
        simtype seed = sim_string_copy(Login::translateKey(password).toUtf8().data());
        simres = sim_key_generate_(&seed, sim_nil(), 0, SIM_KEY_NONE);
        sim_string_free(seed);
        if (simres == SIM_OK) {
          QCoreApplication::postEvent(mc_simcore, new KeygenEvent(0, SIM_OK, -1, "init"));
          return true;
        }
        if (!SimParam::get("ui.login.showpass")) sim_error_set_text(0, 0, 0, simres);
      }

      QString simerr = getError(simres);
      if (prompt) simerr.append("\n\n").append(tr("Enter your secret key and try again."));
      execMessageBox(true, tr("Login not successful (%1)").arg(simres), simerr);
    }
  }
  if (simres == SIM_KEY_BAD_KEY || !loginUser(false)) Contacts::get()->doQuit();

  mc_startupMinimized = false;
  return false;
}

void SimCore::logoutStart(bool quit)
{
  unregisterHotKey(2);
  unregisterHotKey(1);
  m_hwnd = 0;
  mc_calling = false;
  m_logged = false;
  m_speechTimer.stop();
  m_latencyTimer.stop();
  m_hangupTimer.stop();
  if (m_latencyDialog) m_latencyDialog->reject();
  if (m_speechDialog) m_speechDialog->reject();
  SoundEffects::stopSounds(-1); // this is necessary because audio events are not processed after this point
  for (unsigned i = m_contacts.size(); i--;) {
    delete m_contacts[i];
    m_contacts[i] = 0;
  }
  startThread(QString(), quit ? SimThread::thread_quit : SimThread::thread_logout);
}

#ifndef HAVE_LIBPTH
static QString getLoginMessage(bool * checkboxed)
{
  const char * type = g_tickEvent;
  if (!type) return SimCore::tr("Simphone is loading the list of your contacts");
  if (!strcmp(type, SIM_EVENT_CONTACT)) return SimCore::tr("Simphone is loading your configuration");
  if (!strcmp(type, SIM_EVENT_STATUS)) return SimCore::tr("Simphone is loading the list of your files");
  if (!strcmp(type, SIM_EVENT_XFER)) return SimCore::tr("Simphone is erasing cancelled files");
  if (!strcmp(type, SIM_EVENT_HISTORY)) return SimCore::tr("Simphone is loading your chat history");
  if (strcmp(type, SIM_EVENT_KEYGEN)) return SimCore::tr("Simphone is logging you in");
  *checkboxed = true;
  return SimCore::tr("Simphone is checking your RSA key by applying a Rabin-Miller test");
}
#endif

int SimCore::startThread(const QString & argStr, SimThread::E_ThreadType action)
{
  int simres = SIM_OK;

  if (!m_simThread || m_simThread->m_action == SimThread::thread_login) {
    if (m_simThread) m_simThread->wait();
    m_simThread = new SimThread(action, argStr);
    if (action != SimThread::thread_login) {
      connect(m_simThread, SIGNAL(finished()), this, SLOT(onLogout()));
    }
  }
#ifndef HAVE_LIBPTH
  m_simThread->start();
  if (action == SimThread::thread_login) {
    int timeout = 2000;
    if (mc_hidden != 'h' && !mc_startupMinimized && !m_simThread->wait(timeout)) {
      QAbstractEventDispatcher * dispatcher = QAbstractEventDispatcher::instance();
      QCheckBox cbox(tr("My secret key is secure. Do not validate it again, so I login faster."));
      QMessageBox mbox(QMessageBox::NoIcon, tr("Simphone information"), QString(), QMessageBox::Ok);
      cbox.setFont(qApp->font("QMessageBox"));
      qtfix::fixCheckBoxIcon(&cbox, cbox.font());
      cbox.setFocusPolicy(Qt::NoFocus);
      mbox.setWindowFlags(Qt::WindowMinimizeButtonHint | Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint);
      mbox.button(QMessageBox::Ok)->hide();
      while ((m_simThread && m_simThread->isRunning()) || timeout >= 0) {
        if (!mbox.checkBox() || timeout < 0) {
          bool checkboxed = false;
          QString message = "<br/><p><center>" + tr("<b>Please stand by...</b>") + "</center></p><p><center>";
          mbox.setText(message.append(getLoginMessage(&checkboxed)).append(".</center></p><br/><br/>"));
          mbox.setCheckBox(checkboxed ? &cbox : 0);
          if (mbox.isHidden()) timeout = 2000;
        }
        mbox.show();
        if (!dispatcher || !dispatcher->processEvents(QEventLoop::AllEvents)) QThread::msleep(50);
        timeout -= 50;
      }
      if (cbox.isChecked()) SimParam::set("crypto.validate", 1, false);
      mbox.setCheckBox(0);
    } else {
      m_simThread->wait();
    }
#else
  if (action != SimThread::thread_login) SimParam::set("client.logoff", 1, true);
  m_simThread->run();
  if (action != SimThread::thread_login) {
    onLogout();
  } else {
#endif
    if (m_simThread) {
      simres = m_simThread->m_simres;
      sim_error_set_text(m_simThread->m_argStr.toUtf8().data(), 0, 0, simres);
    }
    delete m_simThread;
    m_simThread = 0;
  }
  return simres;
}

void SimCore::onLogout()
{
  SimThread::E_ThreadType action = m_simThread->m_action;

  log_debug_("ui", "event %s\n", action == SimThread::thread_quit ? "quit" : "logout");

  delete m_simThread;
  m_simThread = 0;

  m_contacts.clear();
  m_idMap.clear();
  m_myId = -1;

  log_debug_("ui", "event %s finished\n", action == SimThread::thread_quit ? "quit" : "logout");
  Contacts::get()->clearModel();
  if (action == SimThread::thread_quit) {
    Contacts::get()->doQuit();
  } else {
    mc_autolog = true;
    loginError(SIM_OK, "", false);
  }
}

void SimCore::onLogin()
{
#ifdef HAVE_LIBPTH
  int msec = SimParam::get("ui.main.core");

  if (!msec) {
    execMessageBox(true, tr("Simphone was compiled without thread support"), getErrorBuffer(SIM_API_BAD_VERSION));
    Contacts::get()->doQuit();
    return;
  }
  m_coreTimer.start(msec);
#else
  if (SimParam::get("ui.main.core")) {
    execMessageBox(true, tr("simcore was compiled without thread support"), getErrorBuffer(SIM_API_BAD_VERSION));
    Contacts::get()->doQuit();
    return;
  }
#endif
  if (!loginStart(mc_startupPass.toUtf8().data(), mc_startupPass)) Contacts::get()->doQuit();
}

void SimCore::onRPCmessageReceived(const QString & message)
{
  log_debug_("ui", "%s %s\n", __FUNCTION__, message.toUtf8().data());
  QStringList list = message.split(' ');

  if (list.size() >= 2) {
    if (list[1] == SINGLE_MESSAGE_SHOW) {
      if (m_login) {
#ifdef __APPLE__
        qtfix::showActivateWindow(Contacts::get(), true);
        Contacts::get()->hide();
        qtfix::showActivateWindow(m_login, false);
#else
        qtfix::showActivateWindow(m_login, true);
#endif
        killProcess(list[0].toULongLong(), 0);
      } else if (m_logged) {
        int i;
        for (i = 2; i < list.size() && list[i] != "-minimized"; ++i) {}
        if (i < list.size()) {
          qtfix::showMinimizedWindow(Contacts::get(), SimParam::get("ui.main.sleep"));
        } else {
          qtfix::showActivateWindow(Contacts::get(), true);
        }
        killProcess(list[0].toULongLong(), 0);
      } else if (m_simThread && m_simThread->m_action != SimThread::thread_login) {
        log_warn_("ui", "%s terminated\n", __FUNCTION__);
        if (sim_exit_() != SIM_OK) _exit(EXIT_FAILED_1);
      } else {
        log_warn_("ui", "%s terminate\n", __FUNCTION__);
        _exit(sim_exit_() == SIM_OK ? 0 : EXIT_FAILED_2);
        Contacts::get()->doQuit();
      }
    } else if (list[1] == SINGLE_MESSAGE_HIDE) {
      if (m_logged) Contacts::get()->on_actionHideAll_triggered();
    } else if (list[1] == SINGLE_MESSAGE_QUIT && g_terminate_signal) {
      log_warn_("ui", "%s terminated%s by signal %d\n", __FUNCTION__, m_logged ? "" : " login", g_terminate_signal);
      if (m_logged && g_terminate_signal <= 0) {
        Contacts::get()->on_actionQuit_triggered();
      } else if (m_simThread && m_simThread->m_action != SimThread::thread_login) {
        if (sim_exit_() != SIM_OK) _exit(EXIT_FAILED_3);
      } else {
        if (m_logged) Contacts::get()->saveSettings(true);
        _exit(sim_exit_() == SIM_OK ? 0 : EXIT_FAILED_4);
        Contacts::get()->doQuit();
      }
    } else if (mc_rpc && list[1] == SINGLE_MESSAGE_EXEC) {
      int i = message.indexOf(SINGLE_MESSAGE_EXEC " ");
      if (i >= 0) {
        const unsigned l = SIM_STRING_GET_LENGTH_CONST(SINGLE_MESSAGE_EXEC " ");
        int simres = sim_console_exec__(message.mid(i + l).toUtf8().data());
        if (simres != SIM_OK) log_debug_("ui", "%s %s EXEC error %d\n", __FUNCTION__, list[0].toUtf8().data(), simres);
      }
    } else {
      log_warn_("ui", "%s unknown: %s\n", __FUNCTION__, message.toUtf8().data());
    }
  }
}

void SimCore::onTimerTimeout()
{
  while (!pth_thread_yield_(m_coreTimer.interval() / 10 + 1)) {}
}

void SimCore::onSpeechTimeout()
{
  simnumber talkid = sim_audio_check_talking_();
  if (talkid && mc_hidden != 'h' && m_speechProb < SimParam::get("speex.speech.start")) {
    int id = getContactId(talkid, true);
    Contact * c = id < 0 ? 0 : getContact(id);
    if (c && !c->isTest()) {
      QMessageBox mbox(QMessageBox::Warning, tr("Calling %1").arg(c->m_nick),
                       tr("<p><b>Simphone did not hear you talking.</b><br/><br/>"
                          "<span style=\" color:#ff0000;\">Check that your microphone is working, connected"
                          " to your audio device, and not muted.</span><br/><br/>You can ignore this warning if your"
                          " contact hears you talking. Otherwise, you can hang up your audio call and choose the"
                          " correct microphone device from the \"Main\" menu \"Settings\". The <b>Start Audio Test</b>"
                          " button will help you check whether your microphone is working.</p>"),
                       QMessageBox::Ok, getParent(Contacts::get()));
      m_speechDialog = &mbox;
      mbox.exec();
      m_speechDialog = 0;
    }
  }
}

void SimCore::onLatencyTimeout()
{
  if (m_latencyTimer.interval() > 1000) m_latencyTimer.setInterval(1000);
  if (m_latencyCount < m_latencyFilter || m_latencyDialog) {
    sim_contact_ping_(m_latencyId, 0, SIM_PING_BIT_AUDIO | SIM_PING_BIT_PROXY); // update latency value
  }
}

void SimCore::onHangupTimeout()
{
  if (mc_hidden != 'h') {
    if (!m_hangupData->isTest()) mc_callConnection = getConnectionState(m_hangupData->m_id);
    checkAudioHangup(m_hangupData->m_simId);
  }
}

void SimCore::checkAudioLatency(simnumber simId)
{
  int latency = SimParam::get("ui.call.latency");
  simtype contact = sim_contact_get_(simId, CONTACT_BIT_LATENCY);
  simnumber pong = sim_table_get_number(contact, CONTACT_KEY_MS_TO_CLIENT);
  simnumber ping = sim_table_get_number(contact, CONTACT_KEY_MS_FROM_CLIENT);
  bool done = !latency || pong < latency;

  if ((++m_latencyCount >= m_latencyFilter && !done) || m_latencyDialog) {
    QString questn = tr("<br/>If this is caused by your current proxy, you can disconnect from it and try another one."
                        " Do you wish to <b>hang up your audio call</b> and try to connect to another proxy?");
    QString qs = qtfix::getString(sim_table_get_pointer(contact, CONTACT_KEY_NICK));
    QString message = tr("The connection between you and <b>%1</b> seems to be too slow.<br/><br/>"
                         "You should hear each other well, but with a significant delay."
                         " The current latency values are:<br/>");
    message = "<p>" + message.arg(qs.toHtmlEscaped()) + "<br/>";
    if (pong) message.append(tr("Ping from you to peer: %1 ms").arg(pong)).append("<br/>");
    if (ping) message.append(tr("Ping from peer to you: %1 ms").arg(ping)).append("<br/>");
    simnumber ppong = sim_table_get_number(contact, CONTACT_KEY_MS_TO_PROXY);
    simnumber pping = sim_table_get_number(contact, CONTACT_KEY_MS_FROM_PROXY);
    if (ppong) message.append(tr("Ping from you to proxy: %1 ms").arg(ppong)).append("<br/>");
    if (pping) message.append(tr("Ping from peer to proxy: %1 ms").arg(pping)).append("<br/>");
    if (!m_latencyDialog) {
      QMessageBox::StandardButton button = QMessageBox::Ok;
      QMessageBox::StandardButtons buttons = QMessageBox::Ok;
      E_ConnectionState callConnection = getConnectionState(getContactId(simId, true));
      if (callConnection == state_relaying_in || callConnection == state_relayed_in) {
        message.append(questn);
        button = QMessageBox::No;
        buttons = QMessageBox::Yes | QMessageBox::No;
      }
      message.append("</p>");
      QMessageBox mbox(QMessageBox::Information, tr("Calling %1").arg(qs), message, buttons, Contacts::get());
      mbox.setTextInteractionFlags(Qt::TextSelectableByMouse);
      m_latencyDialog = &mbox;
      int result = QMessageBox::Yes;
      mbox.setDefaultButton(button);
      if (mc_hidden != 'h' && (result = mbox.exec()) == QMessageBox::Yes) {
        sim_status_exec_(SIM_STATUS_EXEC_PROXY_BLOCK, 0);
      }
      if (result != QMessageBox::NoButton) m_latencyClosed = true; // set to true only if not closed automatically
      m_latencyDialog = 0;
      log_debug_("ui", "latency dialog closed (count = %d)\n", m_latencyCount);
      done = true;
    } else {
      if (m_latencyDialog->standardButtons() != QMessageBox::Ok) message.append(questn);
      qtfix::updateMessageBox(m_latencyDialog, message.append("</p>"));
      done = false;
    }
  }
  if (done) {
    m_latencyId = 0;
    m_latencyTimer.stop();
    log_debug_("ui", "latency timer stop (count = %d)\n", m_latencyCount);
  }
  sim_contact_free_(contact);
}

void SimCore::checkAudioHangup(simnumber simId)
{
  simtype contact = sim_contact_get_(simId, CONTACT_BIT_STATS);
  simnumber myFrames = sim_table_get_number(contact, CONTACT_KEY_FRAMES_MINE_ALL);
  simnumber myUnread = sim_table_get_number(contact, CONTACT_KEY_FRAMES_MINE_READ);
  simnumber myUnwritten = sim_table_get_number(contact, CONTACT_KEY_FRAMES_MINE_WRITE);
  simnumber myLost = sim_table_get_number(contact, CONTACT_KEY_FRAMES_MINE_LOST);
  simnumber peerFrames = sim_table_get_number(contact, CONTACT_KEY_FRAMES_PEER_ALL);
  simnumber peerUnread = sim_table_get_number(contact, CONTACT_KEY_FRAMES_PEER_READ);
  simnumber peerUnwritten = sim_table_get_number(contact, CONTACT_KEY_FRAMES_PEER_WRITE);
  simnumber peerLost = sim_table_get_number(contact, CONTACT_KEY_FRAMES_PEER_LOST);
  sim_contact_free_(contact);

  QString msg = "<p><b>";
  if (simId == CONTACT_ID_TEST) {
    msg.append(tr("Are you satisfied with the quality of this audio test?"));
  } else {
    msg.append(tr("Are you satisfied with the quality of this audio call?"));
  }
  msg.append("</b><br/><br/>");

  QString qs;
  int errors = SimParam::get("ui.call.errors");

  if ((myFrames && (myUnread * 100 / myFrames >= errors || myUnwritten * 100 / myFrames >= errors)) ||
      (peerFrames && (peerUnread * 100 / peerFrames >= errors || peerUnwritten * 100 / peerFrames >= errors))) {
    qs.sprintf("%.2f%%<br/>", myFrames ? double(myUnread) * 100 / double(myFrames) : 0);
    msg.append(tr("Your read errors: %1").arg(qs));
    qs.sprintf(" %.2f%%<br/>", myFrames ? double(myUnwritten) * 100 / double(myFrames) : 0);
    msg.append(tr("Your write errors: %1").arg(qs));
    if (peerFrames) {
      qs = tr("Peer read errors: %1").arg(qs.sprintf("%.2f%%<br/>", double(peerUnread) * 100 / double(peerFrames)));
      msg.append(qs);
      qs = tr("Peer write errors: %1").arg(qs.sprintf("%.2f%%<br/>", double(peerUnwritten) * 100 / double(peerFrames)));
      msg.append(qs);
    }
    msg.append("<br/>");
    msg.append(tr("If your audio device has a problem, try to fix it as described in the"
                  " user manual <b>Audio Device Troubleshooting</b> chapter from the \"Help\" menu."));
  } else if (simId != CONTACT_ID_TEST &&
             ((myFrames && myLost * 100 / myFrames >= SimParam::get("ui.call.lostframes")) ||
              (peerFrames && peerLost * 100 / peerFrames >= SimParam::get("ui.call.lostframes")))) {
    qs.sprintf("%.2f%%<br/>", myFrames ? double(myLost) * 100 / double(myFrames) : 0);
    msg.append(tr("Your frame loss: %1").arg(qs));
    if (peerFrames) {
      msg.append(tr("Peer frame loss: %1").arg(qs.sprintf("%.2f%%<br/>", double(peerLost) * 100 / double(peerFrames))));
    }
    msg.append("<br/>");
    if (mc_callConnection == state_relaying_in || mc_callConnection == state_relayed_in) {
      msg.append(tr("If you could not hear anything, maybe you have a problem with your proxy connection."
                    " Try to ensure, you have incoming internet connectivity, as described in the"
                    " user manual <b>Network Configuration</b> chapter."));
      msg.append("<br/><br/>");
      msg.append(tr("If this is caused by your current proxy, you can disconnect from it"
                    " and try to connect to another one. Do you wish to do so now?"));
      msg.append("</p>");
      QWidget * parent = getParent(Contacts::get());
      QMessageBox mbox(QMessageBox::Question, tr("Audio Quality"), msg, QMessageBox::Yes | QMessageBox::No, parent);
      mbox.setTextInteractionFlags(Qt::TextSelectableByMouse);
      mbox.setDefaultButton(QMessageBox::No);
      if (mbox.exec() == QMessageBox::Yes) sim_status_exec_(SIM_STATUS_EXEC_PROXY_BLOCK, 0);
      msg = QString();
    } else if (mc_callConnection == state_relaying_out || mc_callConnection == state_relayed_out) {
      msg.append(tr("If you could not hear anything, maybe you have a problem with your proxy connection."
                    " Try to ensure, you have incoming internet connectivity, as described in the"
                    " user manual <b>Network Configuration</b> chapter."));
    } else {
      msg.append(tr("If you could not hear anything, maybe you have a problem with your internet connection."
                    " Try to reduce the <b>Audio call sampling rate</b> from the \"Main\" menu \"Settings\""
                    " and see if there is any improvement."));
    }
  } else {
    msg = QString();
  }

  if (!msg.isEmpty()) {
    QWidget * parent = getParent(Contacts::get());
    QMessageBox mbox(QMessageBox::Information, tr("Audio Quality"), msg.append("</p>"), QMessageBox::Ok, parent);

    mbox.setTextInteractionFlags(Qt::TextSelectableByMouse);
    mbox.exec();
  }
}

#ifdef HAVE_LIBXCB
void HotKeyThread::init(int id, int hotkey)
{
  static const unsigned short masks[] = { 0, XCB_MOD_MASK_LOCK, XCB_MOD_MASK_2, XCB_MOD_MASK_LOCK | XCB_MOD_MASK_2 };
  static const unsigned values[] = { XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_BUTTON_PRESS };

  xcb_connection_t * xcb = (xcb_connection_t *)m_xcb;
  if (!xcb) m_xcb = xcb = xcb_connect(0, 0);

  if (xcb) {
    xcb_key_symbols_t * keysymbols = xcb_key_symbols_alloc(xcb);
    xcb_keycode_t * keycode = xcb_key_symbols_get_keycode(keysymbols, hotkey & 0x7F);
    const xcb_setup_t * setup = xcb_get_setup(xcb);

    if (keycode && setup) {
      xcb_screen_iterator_t screens = xcb_setup_roots_iterator(setup);
      unsigned short mask = 0;

      if (hotkey & 0x80) mask |= XCB_MOD_MASK_5;
      if (hotkey & 0x100) mask |= XCB_MOD_MASK_1;
      if (hotkey & 0x200) mask |= XCB_MOD_MASK_CONTROL;
      if (hotkey & 0x400) mask |= XCB_MOD_MASK_SHIFT;
      if (hotkey & 0x800) mask |= XCB_MOD_MASK_4;
      if (hotkey & 0x1000) mask |= XCB_MOD_MASK_3;

      for (int i = 0; i < screens.rem; i++) {
        if (id > 0) {
          for (unsigned j = 0; j < SIM_ARRAY_SIZE(masks); j++) {
            xcb_grab_key(xcb, 1, screens.data->root, mask | masks[j], *keycode,
                         XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC);
          }
          m_keycode[id - 1] = *keycode;
        } else {
          m_keycode[-1 - id] = 0;
          for (unsigned j = 0; j < SIM_ARRAY_SIZE(masks); j++) {
            xcb_ungrab_key(xcb, *keycode, screens.data->root, mask | masks[j]);
          }
        }
        xcb_change_window_attributes(xcb, screens.data->root, XCB_CW_EVENT_MASK, values);
        xcb_screen_next(&screens);
      }
      xcb_screen_end(screens);
      xcb_flush(xcb);
    }
    if (keycode) free(keycode);
    xcb_key_symbols_free(keysymbols);
  }
}

void HotKeyThread::uninit()
{
  m_xcb = 0;
}

void HotKeyThread::run()
{
  xcb_connection_t * xcb = (xcb_connection_t *)m_xcb;
  while (m_xcb) {
    xcb_generic_event_t * event = xcb_wait_for_event(xcb);

    if (event) {
      if ((event->response_type & ~0x80) == XCB_KEY_PRESS) {
        xcb_key_press_event_t * keypress = (xcb_key_press_event_t *)event;
        if (keypress->detail == m_keycode[0]) {
          QTimer::singleShot(0, Contacts::get(), SLOT(on_actionHideAll_triggered()));
        } else if (keypress->detail == m_keycode[1]) {
          QTimer::singleShot(0, Contacts::get(), SLOT(showSpontaneousWindow()));
        }
      }
      free(event);
    }
  }
  if (xcb) xcb_disconnect(xcb);
}
#else
void HotKeyThread::run()
{}
#endif
