Many of you have probably heard about the services that allow sharing screenshots with one click of a button. The general concept is as follows: you press a hot key, select an area of the screen, and in the clipboard you get a link to a screenshot that is automatically uploaded to some kind of hosting. Also, many of you probably used other services - allowing you to publish a fragment of the source code and get a link to a page displaying this source code with syntax highlighting.
We combined these two ideas and made a project with the following features:
- Publication of screenshots and sources by pressing hotkeys
- Open Source - you can fork / raise on your server / add new features
- Direct link to images, lack of advertising
- Cross platform
- Resistance to high loads (hosted in the cloud)
The service consists of the following components:
')
- Client application
- Server daemon taking screenshots
- Web interface
The client application takes a screenshot and sends it to the server. The server daemon receives the file, generates a unique name for it and writes it to a specific folder. All received files are accessible via the http protocol — nginx is used for this purpose. Nginx + php & fastcgi is also used for the syntax highlighting library.
Client application
For the development of the client application, the Qt framework was chosen. Advantages - good documentation, cross-platform, a large number of libraries. And in general we like him. In addition, we had to use the qxt library for cross-platform work with global hotkeys.
The application consists of the following classes:
- Application
- Network
- Configwidget
- ImageSelectWidget
- LanguageSelectDialog
- Scanhotkeydialog
Application - here is implemented work with global hotkeys, getting screenshots, work with tray and clipboard.
Network - work with the network, i.e. connection to the server, sending data via a simple http-like protocol, receiving a link from the server.
ConfigWidget, ImageSelectWidget, LanguageSelectDialog, ScanHokeyDialog are simple classes that are responsible for the
config window, the image selection window, the programming language, and the hotkeys settings.
During application initialization, the following occurs. First we check if the application is already running. From the cross-platform solutions, the easiest option to check was to run QLocalServer, and try to connect to it at startup. If the connection fails, then this is a new launch and we are working, otherwise we exit.
QLocalSocket socket; socket.connectToServer(APP_NAME); if (socket.waitForConnected(500)) { qDebug() << "Application already launched!"; return false; } _localServer = new QLocalServer(this); if (!_localServer->listen(APP_NAME)) { QLocalServer::removeServer(APP_NAME); _localServer->listen(APP_NAME); }
Then we register hot keys. Using libqxt with hotkeys is pretty easy. You need to create a hotkey, specify a key combination for it and associate with the slot, which will be executed by pressing this hotkey:
_shortcutScreenFull = new QxtGlobalShortcut; if (!_shortcutScreenFull->setShortcut(QKeySequence(fullHotkey))) qDebug() << "Error activating hotkey:" << fullHotkey; connect(_shortcutScreenFull, SIGNAL(activated()), this, SLOT(processScreenshotFull()))
The procedure for obtaining a screenshot looks like. We get a screenshot, save it in the required format in QBuffer, get an array of QByteArray bytes and send it to the server:
QPixmap pixmap = QPixmap::grabWindow(QApplication::desktop()->winId()); QByteArray imageBytes; QBuffer buffer(&imageBytes); buffer.open(QFile::WriteOnly); pixmap.save(&buffer, imagetype.toAscii().constData()); buffer.close(); _network->upload(imageBytes, imagetype);
The protocol for sending the file by the client to the server is as follows. Customer sends:
proto=pastexen version=1.0 type=jpg size=142034 binary_data
Where jpg is the file type; 142034 - file size in bytes; binary_data - file contents
After receiving the file, the server responds:
proto=pastexen version=1.0 url=http:
url - a direct link to a file or to a page with decorated text with syntax highlighting
In the upload method of the Network class, we connect to the server, create a package according to the protocol and transfer it:
_socket.connectToHost(_serverAddr, 9876); _socket.waitForConnected(); QByteArray arr; arr.append("proto=pastexen\n"); arr.append("version=0.2\n"); arr.append("type=" + type + "\n"); arr.append(QString("size=%1\n\n").arg(data.size())); arr.append(data); _socket.write(arr);
As soon as we receive the answer, we extract the link from it, and throw the linkReceived signal:
const QByteArray arr = _socket.readAll(); const QString link = getValue(arr, "url"); emit linkReceived(link); _socket.disconnectFromHost();
We catch the signal in the Application class, copy the received link to the clipboard, show the message in the tray:
QApplication::clipboard()->setText(link); _trayIcon->showMessage(tr("Done!"), tr("File shared!"), QSystemTrayIcon::Information, 6500);
Similarly implemented sending source code.
Server part
The server daemon was also made using Qt. We tested the resulting solution under load (1000 simultaneously transmitted files) and decided that it would suit us.
The server is implemented on the basis of a thread pool. All connected clients are distributed between existing threads, work with clients occurs in asynchronous mode. Saving the received data to files is also carried out in the hotel stream.
The application uses classes:
- Server
- ThreadPool
- Saver
- Socket
In the Server class, a tcp server (QTcpServer) is created, new connections are processed. For the connected client, a Socket is created, which will be executed in one of the threads provided by ThreadPool_.
auto socket = _server.nextPendingConnection(); new Socket(socket, ThreadPool::getNextThread(), it.value());
In addition, when connecting, it checks whether the user has exceeded the limit.
In the constructor of the Socket class, it is transferred to the execution thread passed to it (i.e., all events associated with this class will be processed in the event-loop_e running in another thread). To do this, use the moveToThread function.
Also inside the Socket, the incoming data is processed, a save signal is sent (emit saveFile). This signal will process the class Saver, which is running in another thread. After Saver saves everything to a file, it will send a signal that will process the Socket and send a link to the file to the client.
Hosting
The project was planned from the very beginning as open-source. And when he was almost ready, there was a question about hosting. We needed some kind of reliable hositng that could withstand the habra-effect. It also raised the question of who will pay for this hosting, and whether we will get (financially) tired of supporting the project over time. Therefore, we sent letters to several large companies with a proposal to participate in our project by providing us with hosting. Having received several answers, we settled on a Scalaksi cloud hosting.
The server has been installed debian, nginx, configured rc scripts and automatic restart using monit.
Future of the project
In the next version of the program, we plan to make the following features:
- Add a transfer function by pressing a single button from the clipboard using the translate service, for example from Google.
- Preview pictures.
- Mac build (if someone can build qt applications under a poppy, help wouldn’t hurt)
- Internationalization
- Maybe you can offer something else.
Links
pastexen.com - the official website of the project
github.com/bakwc/Pastexen - sources on github
Scalaxy.ru -
Scalaxi company, which provided cloud hosting to the project