diff --git a/MediaInfoXP.vcxproj b/MediaInfoXP.vcxproj index c44e756..17dc20e 100644 --- a/MediaInfoXP.vcxproj +++ b/MediaInfoXP.vcxproj @@ -130,6 +130,7 @@ + @@ -145,7 +146,14 @@ - + + "$(QTDIR)\bin\moc.exe" -o "$(SolutionDir)\tmp\Common\moc\MOC_%(Filename).cpp" "%(FullPath)" + "$(QTDIR)\bin\moc.exe" -o "$(SolutionDir)\tmp\Common\moc\MOC_%(Filename).cpp" "%(FullPath)" + MOC "$(SolutionDir)\tmp\Common\moc\MOC_%(Filename).cpp" + MOC "$(SolutionDir)\tmp\Common\moc\MOC_%(Filename).cpp" + $(SolutionDir)\tmp\Common\moc\MOC_%(Filename).cpp;%(Outputs) + $(SolutionDir)\tmp\Common\moc\MOC_%(Filename).cpp;%(Outputs) + diff --git a/MediaInfoXP.vcxproj.filters b/MediaInfoXP.vcxproj.filters index 918e0c6..5b7ddbe 100644 --- a/MediaInfoXP.vcxproj.filters +++ b/MediaInfoXP.vcxproj.filters @@ -31,6 +31,9 @@ Header Files + + Header Files + @@ -54,6 +57,9 @@ Source Files + + Source Files\Generated + @@ -65,9 +71,6 @@ Header Files - - Header Files - diff --git a/src/IPC.cpp b/src/IPC.cpp index 463bde2..a52ac7c 100644 --- a/src/IPC.cpp +++ b/src/IPC.cpp @@ -25,7 +25,6 @@ #include #include -#include static const size_t MAX_STR_LEN = 1024; static const size_t MAX_ENTRIES = 16; @@ -39,57 +38,101 @@ typedef struct wchar_t data[MAX_ENTRIES][MAX_STR_LEN]; size_t posRd; size_t posWr; + size_t counter; } mixp_ipc_t; +/////////////////////////////////////////////////////////////////////////////// +// Send Thread +/////////////////////////////////////////////////////////////////////////////// + +IPCSendThread::IPCSendThread(IPC *ipc, const QString &str) +: + m_ipc(ipc), m_str(str) +{ + m_result = false; +} + +void IPCSendThread::run(void) +{ + try + { + m_result = m_ipc->pushStr(m_str); + } + catch(...) + { + m_result = false; + } +} + + +/////////////////////////////////////////////////////////////////////////////// +// Receive Thread +/////////////////////////////////////////////////////////////////////////////// + +IPCReceiveThread::IPCReceiveThread(IPC *ipc) +: + m_ipc(ipc) +{ + m_stopped = false; +} + +void IPCReceiveThread::run(void) +{ + try + { + receiveLoop(); + } + catch(...) + { + qWarning("Exception in IPC receive thread!"); + } +} + +void IPCReceiveThread::receiveLoop(void) +{ + while(!m_stopped) + { + QString temp; + if(m_ipc->popStr(temp)) + { + if(!temp.isEmpty()) + { + emit receivedStr(temp); + } + } + } +} + +/////////////////////////////////////////////////////////////////////////////// +// IPC Class +/////////////////////////////////////////////////////////////////////////////// + IPC::IPC(void) { m_initialized = -1; m_sharedMemory = NULL; m_semaphoreWr = NULL; m_semaphoreRd = NULL; + m_recvThread = NULL; } IPC::~IPC(void) { + if(m_recvThread && m_recvThread->isRunning()) + { + qWarning("Receive thread still running -> terminating!"); + m_recvThread->terminate(); + m_recvThread->wait(); + } + + MIXP_DELETE_OBJ(m_recvThread); MIXP_DELETE_OBJ(m_sharedMemory); MIXP_DELETE_OBJ(m_semaphoreWr); MIXP_DELETE_OBJ(m_semaphoreRd); } -/////////////////////////////////////////////////////////////////////////////// - -class SendThread : public QThread -{ -public: - SendThread(IPC *ipc, const QString &str) : m_ipc(ipc), m_str(str) - { - m_result = false; - } - - virtual void run(void) - { - try - { - m_result = m_ipc->pushStr(m_str); - } - catch(...) - { - m_result = false; - } - } - - inline bool result(void) { return m_result; } - -protected: - volatile bool m_result; - IPC *const m_ipc; - const QString m_str; -}; - -/////////////////////////////////////////////////////////////////////////////// - -int IPC::init(void) +int IPC::initialize(void) { if(m_initialized >= 0) { @@ -148,11 +191,22 @@ bool IPC::pushStr(const QString &str) return false; } + bool success = true; + try { mixp_ipc_t *memory = (mixp_ipc_t*) m_sharedMemory->data(); - wcsncpy_s(memory->data[memory->posWr], MAX_STR_LEN, (wchar_t*)str.utf16(), _TRUNCATE); - memory->posWr = (memory->posWr + 1) % MAX_ENTRIES; + if(memory->counter < MAX_ENTRIES) + { + wcsncpy_s(memory->data[memory->posWr], MAX_STR_LEN, (wchar_t*)str.utf16(), _TRUNCATE); + memory->posWr = (memory->posWr + 1) % MAX_ENTRIES; + memory->counter++; + } + else + { + qWarning("IPC: Shared memory is full -> cannot push string!"); + success = false; + } } catch(...) { @@ -160,9 +214,13 @@ bool IPC::pushStr(const QString &str) } m_sharedMemory->unlock(); - m_semaphoreRd->release(); - return true; + if(success) + { + m_semaphoreRd->release(); + } + + return success; } bool IPC::popStr(QString &str) @@ -185,11 +243,23 @@ bool IPC::popStr(QString &str) return false; } + bool success = true; + try { mixp_ipc_t *memory = (mixp_ipc_t*) m_sharedMemory->data(); - str = QString::fromUtf16((const ushort*)memory->data[memory->posRd]); - memory->posRd = (memory->posRd + 1) % MAX_ENTRIES; + if(memory->counter > 0) + { + memory->data[memory->posRd][MAX_STR_LEN-1] = L'\0'; + str = QString::fromUtf16((const ushort*)memory->data[memory->posRd]); + memory->posRd = (memory->posRd + 1) % MAX_ENTRIES; + memory->counter--; + } + else + { + qWarning("IPC: Shared memory is empty -> cannot pop string!"); + success = false; + } } catch(...) { @@ -197,14 +267,18 @@ bool IPC::popStr(QString &str) } m_sharedMemory->unlock(); - m_semaphoreWr->release(); - return true; + if(success) + { + m_semaphoreWr->release(); + } + + return success; } bool IPC::sendAsync(const QString &str, const int timeout) { - SendThread sendThread(this, str); + IPCSendThread sendThread(this, str); sendThread.start(); if(!sendThread.wait(timeout)) @@ -217,3 +291,42 @@ bool IPC::sendAsync(const QString &str, const int timeout) return sendThread.result(); } + +void IPC::startListening(void) +{ + if(!m_recvThread) + { + m_recvThread = new IPCReceiveThread(this); + connect(m_recvThread, SIGNAL(receivedStr(QString)), this, SIGNAL(receivedStr(QString)), Qt::QueuedConnection); + } + + if(!m_recvThread->isRunning()) + { + m_recvThread->start(); + } + else + { + qWarning("Receive thread was already running!"); + } + +} + +void IPC::stopListening(void) +{ + if(m_recvThread && m_recvThread->isRunning()) + { + m_recvThread->stop(); + m_semaphoreRd->release(); + + if(!m_recvThread->wait(5000)) + { + qWarning("Receive thread seems deadlocked -> terminating!"); + m_recvThread->terminate(); + m_recvThread->wait(); + } + } + else + { + qWarning("Receive thread was not running!"); + } +} diff --git a/src/IPC.h b/src/IPC.h index cc93952..5c6667d 100644 --- a/src/IPC.h +++ b/src/IPC.h @@ -21,30 +21,80 @@ #pragma once -#include +#include class QSharedMemory; class QSystemSemaphore; +class IPCSendThread; +class IPCReceiveThread; class IPC : public QObject { + Q_OBJECT + friend class IPCReceiveThread; + friend class IPCSendThread; + public: IPC(void); ~IPC(void); - int init(void); - - //async support + int initialize(void); bool sendAsync(const QString &str, const int timeout = 5000); - - //blocking operations - bool pushStr(const QString &str); - bool popStr(QString &str); + +public slots: + void startListening(void); + void stopListening(void); + +signals: + void receivedStr(const QString &str); protected: + bool popStr(QString &str); + bool pushStr(const QString &str); + int m_initialized; QSharedMemory *m_sharedMemory; QSystemSemaphore *m_semaphoreRd; QSystemSemaphore *m_semaphoreWr; + IPCReceiveThread *m_recvThread; +}; + +/////////////////////////////////////////////////////////////////////////////// + +class IPCSendThread : public QThread +{ + Q_OBJECT + friend class IPC; + +protected: + IPCSendThread(IPC *ipc, const QString &str); + inline bool result(void) { return m_result; } + + virtual void run(void); + +private: + volatile bool m_result; + IPC *const m_ipc; + const QString m_str; +}; + +class IPCReceiveThread : public QThread +{ + Q_OBJECT + friend class IPC; + +protected: + IPCReceiveThread(IPC *ipc); + inline void stop(void) { m_stopped = true; } + + virtual void run(void); + +signals: + void receivedStr(const QString &str); + +private: + void receiveLoop(void); + volatile bool m_stopped; + IPC *const m_ipc; }; diff --git a/src/Main.cpp b/src/Main.cpp index 7cc9273..8cb8b79 100644 --- a/src/Main.cpp +++ b/src/Main.cpp @@ -102,24 +102,45 @@ int mixp_main(int argc, char* argv[]) qDebug("Copyright (c) 2004-%s LoRd_MuldeR . Some rights reserved.", &mixp_buildDate[7]); qDebug("Built with Qt v%s, running with Qt v%s.\n", QT_VERSION_STR, qVersion()); - //Initialize IPC + //Create application + QApplication *application = new QApplication(argc, argv); + + //Create IPC IPC *ipc = new IPC(); - if(ipc->init() == 0) + + //Is this the *first* instance? + if(ipc->initialize() == 0) { - ipc->sendAsync("Test Hello World 123!"); - return 0; + //We are *not* the first instance -> pass all file names to the running instance + const QStringList arguments = qApp->arguments(); + bool bHaveFile = false; + for(QStringList::ConstIterator iter = arguments.constBegin(); iter != arguments.constEnd(); iter++) + { + if(QString::compare(*iter, "--open", Qt::CaseInsensitive) == 0) + { + if(++iter != arguments.constEnd()) + { + if(ipc->sendAsync(*iter)) + { + bHaveFile = true; + continue; + } + } + break; + } + } + //If no file was sent, we will at least try to bring the other instance to front + if(!bHaveFile) + { + ipc->sendAsync("?"); + } + MIXP_DELETE_OBJ(ipc); + MIXP_DELETE_OBJ(application); + return 42; } - QString test; - qDebug("Awaiting data from other instance..."); - if(ipc->popStr(test)) - { - qDebug("Got the data: %s\n", test.toUtf8().constData()); - } - - QFile *lockFile = NULL; - //Get temp folder + QFile *lockFile = NULL; const QString tempFolder = mixp_getTempFolder(&lockFile); if(tempFolder.isEmpty()) { @@ -130,18 +151,17 @@ int mixp_main(int argc, char* argv[]) qDebug("TEMP folder is:\n%s\n", QDir::toNativeSeparators(tempFolder).toUtf8().constData()); - //Create application - QApplication *application = new QApplication(argc, argv); - application->setWindowIcon(QIcon(":/QtTestApp.ico")); - //Create main window - CMainWindow *mainWindow = new CMainWindow(tempFolder); + CMainWindow *mainWindow = new CMainWindow(tempFolder, ipc); mainWindow->show(); //Run application const int exit_code = application->exec(); qDebug("\nTime to say goodbye... (%d)\n", exit_code); + //Stop IPC + ipc->stopListening(); + //Clean up MIXP_DELETE_OBJ(mainWindow); MIXP_DELETE_OBJ(application); diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index fea8a89..9354472 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -39,18 +39,16 @@ //CRT #include -//Win32 -//#define WIN32_LEAN_AND_MEAN -//#include - //Internal #include "Config.h" #include "Utils.h" #include "ShellExtension.h" +#include "IPC.h" //Macros #define SET_FONT_BOLD(WIDGET,BOLD) { QFont _font = WIDGET->font(); _font.setBold(BOLD); WIDGET->setFont(_font); } #define SET_TEXT_COLOR(WIDGET,COLOR) { QPalette _palette = WIDGET->palette(); _palette.setColor(QPalette::WindowText, (COLOR)); _palette.setColor(QPalette::Text, (COLOR)); WIDGET->setPalette(_palette); } +#define APPLICATION_IS_IDLE (m_status == APP_STATUS_IDLE) //Text const char *STATUS_BLNK = ">> You can drop any type of media files here <<"; @@ -71,16 +69,20 @@ static QList> HTML_ESCAPE(void) return htmlEscape; } +//Const +static const int FILE_RECEIVE_DELAY = 1750; + //////////////////////////////////////////////////////////// // Constructor //////////////////////////////////////////////////////////// -CMainWindow::CMainWindow(const QString &tempFolder, QWidget *parent) +CMainWindow::CMainWindow(const QString &tempFolder, IPC *const ipc, QWidget *parent) : QMainWindow(parent), m_tempFolder(tempFolder), - m_firstShow(true), + m_ipc(ipc), m_htmlEscape(HTML_ESCAPE()), + m_status(APP_STATUS_STARTING), ui(new Ui::MainWindow) { //Init UI @@ -107,6 +109,7 @@ CMainWindow::CMainWindow(const QString &tempFolder, QWidget *parent) connect(ui->actionLink_Discuss, SIGNAL(triggered()), this, SLOT(linkTriggered())); connect(ui->actionAbout, SIGNAL(triggered()), this, SLOT(showAboutScreen())); connect(ui->actionShellExtension, SIGNAL(toggled(bool)), this, SLOT(updateShellExtension(bool))); + connect(m_ipc, SIGNAL(receivedStr(QString)), this, SLOT(fileReceived(QString))); ui->versionLabel->installEventFilter(this); //Context menu @@ -168,10 +171,7 @@ void CMainWindow::showEvent(QShowEvent *event) resizeEvent(NULL); QTimer::singleShot(0, this, SLOT(updateSize())); - //Enable drag & drop support - setAcceptDrops(true); - - if(m_firstShow) + if(m_status == APP_STATUS_STARTING) { const QStringList arguments = qApp->arguments(); for(QStringList::ConstIterator iter = arguments.constBegin(); iter != arguments.constEnd(); iter++) @@ -192,19 +192,28 @@ void CMainWindow::showEvent(QShowEvent *event) } if(!m_pendingFiles.empty()) { - QTimer::singleShot(0, this, SLOT(analyzeFiles())); + m_status = APP_STATUS_AWAITING; + QTimer::singleShot(FILE_RECEIVE_DELAY, this, SLOT(analyzeFiles())); } - QTimer::singleShot(1250, this, SLOT(initShellExtension())); - m_firstShow = false; + QTimer::singleShot(125, m_ipc, SLOT(startListening())); + QTimer::singleShot(250, this, SLOT(initShellExtension())); + + if(m_status == APP_STATUS_STARTING) + { + m_status = APP_STATUS_IDLE; + } } + + //Enable drag & drop support + setAcceptDrops(true); } void CMainWindow::closeEvent(QCloseEvent *event) { if(m_process) { - if(m_process->state() != QProcess::NotRunning) + if(!APPLICATION_IS_IDLE) { event->ignore(); } @@ -238,9 +247,9 @@ void CMainWindow::dragEnterEvent(QDragEnterEvent *event) void CMainWindow::dropEvent(QDropEvent *event) { - if(m_process && (m_process->state() != QProcess::NotRunning)) + if(!APPLICATION_IS_IDLE) { - qWarning("Process is still running!\n"); + qWarning("Cannot process files at this time!\n"); return; } @@ -259,6 +268,7 @@ void CMainWindow::dropEvent(QDropEvent *event) if(!m_pendingFiles.isEmpty()) { + m_status = APP_STATUS_WORKING; QTimer::singleShot(0, this, SLOT(analyzeFiles())); } } @@ -318,6 +328,8 @@ void CMainWindow::analyzeFiles(void) return; } + m_status = APP_STATUS_WORKING; + //Clear data ui->textBrowser->clear(); m_outputLines.clear(); @@ -335,10 +347,7 @@ void CMainWindow::analyzeFiles(void) m_floatingLabel->show(); m_floatingLabel->setText(QString::fromLatin1(STATUS_WORK)); m_floatingLabel->setCursor(Qt::WaitCursor); - - //Trigger GUI update - QApplication::processEvents(QEventLoop::ExcludeUserInputEvents); - + //Give it a go! QTimer::singleShot(0, this, SLOT(analyzeNextFile())); } @@ -351,6 +360,13 @@ void CMainWindow::analyzeNextFile(void) return; } + //Still running? + if(m_process->state() != QProcess::NotRunning) + { + qWarning("Process is still running!\n"); + return; + } + //Lookup MediaInfo path const QString mediaInfoPath = getMediaInfoPath(); if(mediaInfoPath.isEmpty()) @@ -362,6 +378,7 @@ void CMainWindow::analyzeNextFile(void) ui->analyzeButton->setEnabled(true); ui->exitButton->setEnabled(true); ui->menuPreferences->setEnabled(true); + m_status = APP_STATUS_IDLE; return; } @@ -387,6 +404,7 @@ void CMainWindow::analyzeNextFile(void) ui->analyzeButton->setEnabled(true); ui->exitButton->setEnabled(true); ui->menuPreferences->setEnabled(true); + m_status = APP_STATUS_IDLE; return; } @@ -395,9 +413,9 @@ void CMainWindow::analyzeNextFile(void) void CMainWindow::analyzeButtonClicked(void) { - if(m_process && (m_process->state() != QProcess::NotRunning)) + if(!APPLICATION_IS_IDLE) { - qWarning("Process is still running!\n"); + qWarning("Cannot process files at this time!\n"); return; } @@ -406,18 +424,19 @@ void CMainWindow::analyzeButtonClicked(void) { m_pendingFiles.clear(); m_pendingFiles << selectedFiles; + m_status = APP_STATUS_WORKING; analyzeFiles(); } } void CMainWindow::saveButtonClicked(void) { - if(m_process && (m_process->state() != QProcess::NotRunning)) + if(!APPLICATION_IS_IDLE) { - qWarning("Process is still running!\n"); + qWarning("Cannot process files at this time!\n"); return; } - + const QString selectedFile = QFileDialog::getSaveFileName(this, tr("Select file to save..."), QString(), tr("Plain Text (*.txt)")); if(!selectedFile.isEmpty()) { @@ -437,9 +456,9 @@ void CMainWindow::saveButtonClicked(void) void CMainWindow::copyToClipboardButtonClicked(void) { - if(m_process && (m_process->state() != QProcess::NotRunning)) + if(!APPLICATION_IS_IDLE) { - qWarning("Process is still running!\n"); + qWarning("Cannot process files at this time!\n"); return; } @@ -452,9 +471,9 @@ void CMainWindow::copyToClipboardButtonClicked(void) void CMainWindow::clearButtonClicked(void) { - if(m_process && (m_process->state() != QProcess::NotRunning)) + if(!APPLICATION_IS_IDLE) { - qWarning("Process is still running!\n"); + qWarning("Cannot process files at this time!\n"); return; } @@ -551,6 +570,8 @@ void CMainWindow::processFinished(void) ui->analyzeButton->setEnabled(true); ui->exitButton->setEnabled(true); ui->menuPreferences->setEnabled(true); + + m_status = APP_STATUS_IDLE; } void CMainWindow::initShellExtension(void) @@ -584,9 +605,9 @@ void CMainWindow::linkTriggered(void) void CMainWindow::showAboutScreen(void) { - if(m_process && (m_process->state() != QProcess::NotRunning)) + if(!APPLICATION_IS_IDLE) { - qWarning("Process is still running!\n"); + qWarning("Cannot process files at this time!\n"); return; } @@ -645,6 +666,38 @@ void CMainWindow::updateSize(void) } } +void CMainWindow::fileReceived(const QString &str) +{ + mixp_bring_to_front(this); + + if(str.compare("?") != 0) + { + qDebug("Received file: %s", str.toUtf8().constData()); + + if((m_status != APP_STATUS_IDLE) && (m_status != APP_STATUS_AWAITING)) + { + qWarning("Cannot process files at this time!\n"); + return; + } + + const QString absPath = QFileInfo(QDir::fromNativeSeparators(str)).absoluteFilePath(); + QFileInfo fileInfo(absPath); + if(fileInfo.exists() && fileInfo.isFile()) + { + m_pendingFiles << fileInfo.canonicalFilePath(); + if(m_status == APP_STATUS_IDLE) + { + m_status = APP_STATUS_AWAITING; + QTimer::singleShot(FILE_RECEIVE_DELAY, this, SLOT(analyzeFiles())); + } + } + } + else + { + qDebug("Received ping from another instance!"); + } +} + //////////////////////////////////////////////////////////// // PRIVATE FUNCTIONS //////////////////////////////////////////////////////////// diff --git a/src/MainWindow.h b/src/MainWindow.h index f3e9035..447e285 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -32,6 +32,7 @@ class QProcess; class QLabel; class QFile; class mixp_icon_t; +class IPC; //MainWindow class class CMainWindow: public QMainWindow @@ -39,7 +40,7 @@ class CMainWindow: public QMainWindow Q_OBJECT public: - CMainWindow(const QString &tempFolder, QWidget *parent = 0); + CMainWindow(const QString &tempFolder, IPC *const ipc, QWidget *parent = 0); ~CMainWindow(void); private slots: @@ -56,6 +57,7 @@ private slots: void updateSize(void); void initShellExtension(void); void updateShellExtension(bool checked); + void fileReceived(const QString &path); protected: virtual void showEvent(QShowEvent *event); @@ -67,16 +69,27 @@ protected: virtual void keyPressEvent(QKeyEvent *e); private: + enum + { + APP_STATUS_STARTING = 0, + APP_STATUS_IDLE = 1, + APP_STATUS_AWAITING = 2, + APP_STATUS_WORKING = 3 + } + status_t; + Ui::MainWindow *ui; //for Qt UIC + int m_status; const QString &m_tempFolder; - bool m_firstShow; + QFile *m_mediaInfoHandle; QProcess *m_process; QLabel *m_floatingLabel; QStringList m_pendingFiles; QStringList m_outputLines; mixp_icon_t *m_icon; + IPC *const m_ipc; const QList> m_htmlEscape; diff --git a/src/Utils.cpp b/src/Utils.cpp index b3ce35e..ad7f5d9 100644 --- a/src/Utils.cpp +++ b/src/Utils.cpp @@ -478,6 +478,26 @@ bool mixp_beep(int beepType) } } +/* + * Bring the specifed window to the front + */ +bool mixp_bring_to_front(const QWidget *window) +{ + bool ret = false; + + if(window) + { + for(int i = 0; (i < 5) && (!ret); i++) + { + ret = (SetForegroundWindow(window->winId()) != FALSE); + SwitchToThisWindow(window->winId(), TRUE); + } + LockSetForegroundWindow(LSFW_LOCK); + } + + return ret; +} + /* * Registry root key */ diff --git a/src/Utils.h b/src/Utils.h index 3c14eaa..dd03679 100644 --- a/src/Utils.h +++ b/src/Utils.h @@ -60,6 +60,7 @@ QDate mixp_get_current_date(void); mixp_icon_t *mixp_set_window_icon(QWidget *window, const QIcon &icon, const bool bIsBigIcon); void mixp_free_window_icon(mixp_icon_t *icon); bool mixp_beep(int beepType); +bool mixp_bring_to_front(const QWidget *window); void mixp_shell_change_notification(void); //Regsitry