// // ClientKit for Perforce // // Easily create custom Perforce clients with a wide range of capabilities using Qt and web technologies // // The starting page for a web application can be stored in a web server, a local file, // a Perforce server, or a QResource. A QResource could be complied into the EXE for maximum security, // or linked at runtime. The EXE could be constrained to accept QResource files that contain a matching // secret key. // // A function pointer exposed to the JavaScript provides API access to both configuring the ClientKit // container and running Perforce server commands // // This program should make use of no proprietary Perforce knowledge, // and should be able to be duplicated by a programmer with no special Perforce knowledge. // // Thin Client, from http://doc.qt.nokia.com/4.7-snapshot/qtwebkit-bridge.html // Another use case is using Qt as a native backend to a full web application, referred to // here as a thin client. In this use-case, the entire UI is driven by HTML, JavaScript and // CSS, and native Qt-based components are used to allow that application access to native // features not usually exposed to the web, or to enable helper components that are best // written with C++. // #include "mainwindow.h" #include <QVBoxLayout> #include <QLabel> #include <QtWebKit> #include <QFrame> #include <QUrl> #include <QFile> #include <QVariant> #include <QDir> #include <QCoreApplication> #include <iostream> #include "clientapi.h" #include "enviro.h" #pragma comment(lib, "winmm.lib") // these 3 for building on windows #pragma comment(lib, "wsock32.lib") #pragma comment(lib, "advapi32.lib") #include "KCommon.h" static const QString P4PointerName = "p4"; MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { mMainWidget = new QFrame( this ); QVBoxLayout* pageLayout = new QVBoxLayout(); QWebView* webview = new QWebView(this); mWindowStyle = "normal"; mWindowStyles << "normal" << "transparent" << "invisible"; mWebPage = new QWebPage(this); // Manage capabilities of JavaScript code here to fit your security model mWebPage->settings()->setAttribute(QWebSettings::JavascriptEnabled, true); mWebPage->settings()->setAttribute(QWebSettings::JavascriptCanOpenWindows, true); mWebPage->settings()->setAttribute(QWebSettings::JavascriptCanAccessClipboard, true); mWebPage->settings()->setAttribute(QWebSettings::OfflineStorageDatabaseEnabled, true); mWebPage->settings()->setAttribute(QWebSettings::OfflineWebApplicationCacheEnabled, true); mWebPage->settings()->setAttribute(QWebSettings::LocalStorageEnabled, true); // mWebPage->settings()->setAttribute(QWebSettings::LocalContentCanAccessRemoteUrls, true); // mWebPage->settings()->setAttribute(QWebSettings::LocalContentCanAccessFileUrls, true); mWebPage->settings()->setAttribute(QWebSettings::SpatialNavigationEnabled, true); createClientKitApi(P4PointerName); QWebFrame* frame = mWebPage->mainFrame(); mWebPage->settings()->setAttribute(QWebSettings::DeveloperExtrasEnabled, true); // add to API connect(frame, SIGNAL(javaScriptWindowObjectCleared()), this, SLOT(handleJavaScriptWindowObjectCleared())); webview->setPage(mWebPage); // Bootstrap: 1. first we look for a configuration file in .././conf/clientkit.conf from the .exe file location QString appDirPath = QCoreApplication::applicationDirPath(); QDir confDir = QDir(appDirPath); confDir.cdUp(); QString confFilename = confDir.filePath("clientkit.conf"); // once clientkit.exe is installed in a \bin dir, change this to look in a sibling dir called 'config' QString startFilename; QString startFileContents; if (QFile::exists(confFilename)) { QString confFileContents = readFile(confFilename); QMap<QString, QString> settingsMap = parseConfFile(confFileContents); if (settingsMap.contains("startFile")) startFilename = settingsMap["startFile"]; } else { // Bootstrap: 2. Then we look for an index.html in the current directory QString currentDir = QDir::currentPath(); QString defaultIndex = currentDir + "/" + "index.html"; // fixme: don't do string manipulation startFilename = defaultIndex; } // Here we have the filename we want to try to load QFile startFile(startFilename); // Security tip: constrain the type of file that can be loaded here. For example, could disallow loading local files if (!startFile.exists()) // Bootstrap: 3. use default message { startFileContents = "<html><body><h2>ClientKit did not find a clientkit.conf file in the executable directory or an index.html file in the current directory</h2><p><br>Default startup file not found:"+startFilename+"</p><br>Create this file to define your start page<p><p>You can also create a clientkit.conf file and define<p><p>startFile=<My filepath to an html file><p></body></html>"; webview->setHtml(startFileContents); } else webview->setUrl(startFilename); pageLayout->setContentsMargins(0,0,0,0); pageLayout->addWidget(webview); mMainWidget->setLayout(pageLayout); setCentralWidget( mMainWidget ); } MainWindow::~MainWindow() { } QMap<QString, QString> MainWindow::parseConfFile(QString fileContents) { QMap<QString, QString> configMap; QStringList lines = fileContents.split(QRegExp("(\\r\\n)|(\\n\\r)|\\r|\\n"), QString::SkipEmptyParts); for (int i = 0; i < lines.size(); ++i) { QString line = lines.at(i).toLocal8Bit().constData(); if (line[0] != '#') // ignore comment lines { line.trimmed(); if (!line.isEmpty()) { int pos = line.indexOf(" "); if (pos == -1) continue; QString key = line.left(pos); QString value = line.mid(pos+1); configMap.insert(key, value); } } } return configMap; } bool MainWindow::createClientKitApi(QString pointerName) { ClientKitApi* clientKitApi = new ClientKitApi(this, this, pointerName); mClientKitApiMap.insert(pointerName, clientKitApi); mWebPage->mainFrame()->addToJavaScriptWindowObject(pointerName, clientKitApi); return true; } bool MainWindow::setWindowStyle(QString styleName) { Qt::WindowFlags flags; if (styleName=="transparent") { setStyleSheet("background:transparent;"); setAttribute(Qt::WA_TranslucentBackground, false); flags = Qt::Window; } else if (styleName=="normal") { setStyleSheet("background:white;"); setAttribute(Qt::WA_TranslucentBackground, false); flags = Qt::Window; } else if (styleName=="invisible") { setStyleSheet("background:transparent;"); setAttribute(Qt::WA_TranslucentBackground, true); flags = Qt::FramelessWindowHint; } this->setWindowFlags(flags); this->show(); mWindowStyle = styleName; return true; } void MainWindow::handleJavaScriptWindowObjectCleared() { QWebFrame* frame = mWebPage->mainFrame(); QMapIterator<QString, ClientKitApi*> mapi(mClientKitApiMap); while (mapi.hasNext()) { mapi.next(); frame->addToJavaScriptWindowObject(mapi.key(), mapi.value()); // This the magic that makes the pointer visible to JavaScript // can use evaluateJavaScript here to add JavaScript to execution context } QList<QWebFrame *> childFrames = frame->childFrames(); for(int i = 0; i < childFrames.size(); i++) { QMapIterator<QString, ClientKitApi*> mapi(mClientKitApiMap); while (mapi.hasNext()) { mapi.next(); frame->addToJavaScriptWindowObject(mapi.key(), mapi.value()); // can use evaluateJavaScript here to add JavaScript to execution context } } } QString MainWindow::readFile(QString filename) // currently only reads a local file { QString filecontents; QFile f( filename ); if ( !f.exists() ) { mMessageLog += "File not found: " + filename; return QString(); } f.open( QIODevice::ReadOnly ); QTextStream ts ( &f ); QTextCodec * codec = QTextCodec::codecForName("UTF-8"); ts.setCodec( codec ); filecontents = ts.readAll(); return filecontents; }