├── .gitmodules ├── QlipMon.pro ├── README.md ├── common.pri ├── qlipmon.xml ├── rofi ├── config.cpp ├── config.h ├── qlipdata.cpp ├── qlipdata.h ├── qlipmon_interface.cpp ├── qlipmon_interface.h ├── rofi-qlipmon_global.h ├── rofi.pro └── rofiqlipmon.cpp ├── server ├── config.cpp ├── config.h ├── database.cpp ├── database.h ├── database_entry.cpp ├── database_entry.h ├── dbus.cpp ├── dbus.h ├── main.cpp ├── qlipmon-server.service ├── qlipmon.cpp ├── qlipmon.h ├── qlipmon_adaptor.cpp ├── qlipmon_adaptor.h └── server.pro └── xml2cpp.sh /.gitmodules: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vcalv/qlipmon/505c54f59b3d84344e680370ae3d2826ead4592a/.gitmodules -------------------------------------------------------------------------------- /QlipMon.pro: -------------------------------------------------------------------------------- 1 | include(./common.pri) 2 | 3 | TEMPLATE = subdirs 4 | SUBDIRS = \ 5 | server \ 6 | rofi 7 | 8 | OTHER_FILES += qlipmon.xml arch/PKGBUILD 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Qlipmon 2 | 3 | ## Description 4 | 5 | Clipboard history saver with native rofi plugin and dbus interface. 6 | 7 | There's two components: 8 | 9 | * A server that runs in the background (run from startx or via `systemctl --user (start/enable/disable) qlipmon-server.service`) and saves every clipboard selection in memory 10 | * A rofi plugin that allows you to select previous clipboard selections 11 | 12 | Configuration for the server can be done either via the command line (`qlipmon --help`) or via ini file stored in `$HOME/.config/qlipmon/server.ini` 13 | 14 | The rofi plugin can only be configured via ini file stored in `$HOME/.config/qlipmon/rofi.ini` 15 | 16 | The rofi plugin can be selected by running `rofi -modi qlipmon -show qlipmon`. 17 | 18 | After running each of the components the ini files should be populated with sensible default values. 19 | 20 | There is also a dbus interface that allows you get get previous selections or even change the current one. 21 | 22 | Every time a new selection is made, a dbus broadcast is also emitted allowing you to listen for these event and take any action you want. 23 | Selection broadcast is disabled by default and can be enabled via command line, ini configuration or via dbus interface. 24 | 25 | Selections are saved in memory only and for now no persistence is supported, 26 | 27 | 28 | ## Rationale 29 | 30 | After searching for a rofi plugin for clipboard management I didn't find any that worked properly. 31 | 32 | * In some, the selections were truncated and distinct selections with equal truncations were considered to be identical. 33 | * In others there was no real integration with rofi, meaning you couldn't run it as part of combi for example. 34 | * Others were constantly polling the selection buffer and did not employ an asynchronous approach 35 | * I also wanted the ability to get notified of new selections and take any actions I deemed appropriate. A dbus interface seemed perfect for this. 36 | 37 | 38 | Built using [QT](https://qt.io) 39 | -------------------------------------------------------------------------------- /common.pri: -------------------------------------------------------------------------------- 1 | isEmpty(QLIPMON_DBUS_FQDN){ 2 | QLIPMON_DBUS_FQDN = org.qlipmon 3 | } 4 | 5 | isEmpty(QLIPMON_DBUS_PATH){ 6 | QLIPMON_DBUS_PATH = /Qlipmon 7 | } 8 | 9 | isEmpty(PREFIX){ 10 | PREFIX = /usr 11 | } 12 | 13 | 14 | 15 | DEFINES += QLIPMON_DBUS_FQDN='\'"$$QLIPMON_DBUS_FQDN"\'' 16 | DEFINES += QLIPMON_DBUS_PATH='\'"$$QLIPMON_DBUS_PATH"\'' 17 | 18 | #disable qDebug() in release builds 19 | CONFIG(release, debug|release):DEFINES += QT_NO_DEBUG_OUTPUT 20 | -------------------------------------------------------------------------------- /qlipmon.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /rofi/config.cpp: -------------------------------------------------------------------------------- 1 | #include "config.h" 2 | 3 | #include 4 | #include 5 | 6 | static QSettings getSettings(){ 7 | //QSettings settings(path, QSettings::Format::NativeFormat); 8 | return QSettings("qlipmon", "rofi"); 9 | } 10 | 11 | void Config::load(){ 12 | QSettings settings = getSettings(); 13 | qDebug()<<"loading settings from file "< 5 | 6 | class Config{ 7 | public: 8 | bool duplicates = false; 9 | int kind = -1; // -1 => all; 10 | int numberEntries = 0; // infinite 11 | 12 | //void load(const QString& path); 13 | void load(); 14 | //void save(const QString& path); 15 | void save(); 16 | 17 | ~Config(); 18 | }; 19 | 20 | QDebug &operator<<(QDebug &out, const Config &c); 21 | 22 | #endif // CONFIG_H 23 | -------------------------------------------------------------------------------- /rofi/qlipdata.cpp: -------------------------------------------------------------------------------- 1 | #include "qlipdata.h" 2 | 3 | #include "config.h" 4 | 5 | 6 | RofiData* QlipData::getEntries(){ 7 | Config config; 8 | RofiData *ret = new RofiData; 9 | 10 | config.load(); 11 | qDebug()<<"loaded config "<error = false; 18 | ret->errorString = ""; 19 | ret->entries = reply.value(); 20 | auto &entries = ret->entries; 21 | 22 | qDebug()<<"Got a list with "< 0 && entries.size() > config.numberEntries){ 24 | entries.erase(entries.begin() + config.numberEntries, entries.end()); 25 | } 26 | }else{ 27 | qWarning()<<"Error getting clipboard data: "<error = true; 29 | ret->errorString = reply.error().message(); 30 | } 31 | 32 | return ret; 33 | } 34 | 35 | void QlipData::setText(const QString &txt){ 36 | QlipMonInterface _interface(QLIPMON_DBUS_FQDN, QLIPMON_DBUS_PATH, QDBusConnection::sessionBus(), 0); 37 | Config config; 38 | config.load(); 39 | 40 | _interface.setText(txt, config.kind); 41 | } 42 | -------------------------------------------------------------------------------- /rofi/qlipdata.h: -------------------------------------------------------------------------------- 1 | #ifndef QLIPDATA_H 2 | #define QLIPDATA_H 3 | 4 | #include 5 | 6 | extern "C"{ 7 | #include 8 | } 9 | 10 | #include "qlipmon_interface.h" 11 | 12 | class RofiData{ 13 | public: 14 | QStringList entries; 15 | bool error; 16 | QString errorString; 17 | }; 18 | 19 | class QlipData 20 | { 21 | public: 22 | static RofiData* getEntries(); 23 | static void setText(const QString &txt); 24 | }; 25 | 26 | #endif // QLIPDATA_H 27 | -------------------------------------------------------------------------------- /rofi/qlipmon_interface.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This file was generated by qdbusxml2cpp version 0.8 3 | * Command line was: qdbusxml2cpp -V -i qlipmon_interface.h --classname QlipMonInterface -p :qlipmon_interface.cpp ../qlipmon.xml 4 | * 5 | * qdbusxml2cpp is Copyright (C) 2020 The Qt Company Ltd. 6 | * 7 | * This is an auto-generated file. 8 | * This file may have been hand-edited. Look for HAND-EDIT comments 9 | * before re-generating it. 10 | */ 11 | 12 | #include "qlipmon_interface.h" 13 | /* 14 | * Implementation of interface class QlipMonInterface 15 | */ 16 | 17 | QlipMonInterface::QlipMonInterface(const QString &service, const QString &path, const QDBusConnection &connection, QObject *parent) 18 | : QDBusAbstractInterface(service, path, staticInterfaceName(), connection, parent) 19 | { 20 | } 21 | 22 | QlipMonInterface::~QlipMonInterface() 23 | { 24 | } 25 | 26 | -------------------------------------------------------------------------------- /rofi/qlipmon_interface.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file was generated by qdbusxml2cpp version 0.8 3 | * Command line was: qdbusxml2cpp -V -i ../server/database_entry.h --classname QlipMonInterface -p qlipmon_interface.h: ../qlipmon.xml 4 | * 5 | * qdbusxml2cpp is Copyright (C) 2020 The Qt Company Ltd. 6 | * 7 | * This is an auto-generated file. 8 | * Do not edit! All changes made to it will be lost. 9 | */ 10 | 11 | #ifndef QLIPMON_INTERFACE_H 12 | #define QLIPMON_INTERFACE_H 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include "../server/database_entry.h" 23 | 24 | /* 25 | * Proxy class for interface QLIPMON_DBUS_FQDN 26 | */ 27 | class QlipMonInterface: public QDBusAbstractInterface 28 | { 29 | Q_OBJECT 30 | public: 31 | static inline const char *staticInterfaceName() 32 | { return QLIPMON_DBUS_FQDN; } 33 | 34 | public: 35 | QlipMonInterface(const QString &service, const QString &path, const QDBusConnection &connection, QObject *parent = nullptr); 36 | 37 | ~QlipMonInterface(); 38 | 39 | Q_PROPERTY(bool broadcast READ broadcast WRITE setBroadcast) 40 | inline bool broadcast() const 41 | { return qvariant_cast< bool >(property("broadcast")); } 42 | inline void setBroadcast(bool value) 43 | { setProperty("broadcast", QVariant::fromValue(value)); } 44 | 45 | public Q_SLOTS: // METHODS 46 | inline QDBusPendingReply > getHistory() 47 | { 48 | QList argumentList; 49 | return asyncCallWithArgumentList(QStringLiteral("getHistory"), argumentList); 50 | } 51 | 52 | inline QDBusPendingReply getLastText() 53 | { 54 | QList argumentList; 55 | return asyncCallWithArgumentList(QStringLiteral("getLastText"), argumentList); 56 | } 57 | 58 | inline QDBusPendingReply getLastText(int mode) 59 | { 60 | QList argumentList; 61 | argumentList << QVariant::fromValue(mode); 62 | return asyncCallWithArgumentList(QStringLiteral("getLastText"), argumentList); 63 | } 64 | 65 | inline QDBusPendingReply getTextHistory() 66 | { 67 | QList argumentList; 68 | return asyncCallWithArgumentList(QStringLiteral("getTextHistory"), argumentList); 69 | } 70 | 71 | inline QDBusPendingReply getTextHistory(int mode) 72 | { 73 | QList argumentList; 74 | argumentList << QVariant::fromValue(mode); 75 | return asyncCallWithArgumentList(QStringLiteral("getTextHistory"), argumentList); 76 | } 77 | 78 | inline QDBusPendingReply getTextHistory(int mode, bool duplicates) 79 | { 80 | QList argumentList; 81 | argumentList << QVariant::fromValue(mode) << QVariant::fromValue(duplicates); 82 | return asyncCallWithArgumentList(QStringLiteral("getTextHistory"), argumentList); 83 | } 84 | 85 | inline QDBusPendingReply<> setText(const QString &text) 86 | { 87 | QList argumentList; 88 | argumentList << QVariant::fromValue(text); 89 | return asyncCallWithArgumentList(QStringLiteral("setText"), argumentList); 90 | } 91 | 92 | inline QDBusPendingReply<> setText(const QString &text, int mode) 93 | { 94 | QList argumentList; 95 | argumentList << QVariant::fromValue(text) << QVariant::fromValue(mode); 96 | return asyncCallWithArgumentList(QStringLiteral("setText"), argumentList); 97 | } 98 | 99 | Q_SIGNALS: // SIGNALS 100 | void updated(const QString &text, int mode); 101 | }; 102 | 103 | namespace DOMAIN { 104 | typedef ::QlipMonInterface DOMAIN; 105 | } 106 | #endif 107 | -------------------------------------------------------------------------------- /rofi/rofi-qlipmon_global.h: -------------------------------------------------------------------------------- 1 | #ifndef ROFIQLIPMON_GLOBAL_H 2 | #define ROFIQLIPMON_GLOBAL_H 3 | 4 | #include 5 | 6 | #if defined(ROFIQLIPMON_LIBRARY) 7 | # define ROFIQLIPMON_EXPORT Q_DECL_EXPORT 8 | #else 9 | # define ROFIQLIPMON_EXPORT Q_DECL_IMPORT 10 | #endif 11 | 12 | #endif // ROFIQLIPMON_GLOBAL_H 13 | -------------------------------------------------------------------------------- /rofi/rofi.pro: -------------------------------------------------------------------------------- 1 | include(../common.pri) 2 | 3 | TARGET = qlipmon 4 | 5 | QT = dbus 6 | 7 | TEMPLATE = lib 8 | DEFINES += ROFIQLIPMON_LIBRARY 9 | 10 | CONFIG += plugin 11 | CONFIG += no_plugin_name_prefix 12 | CONFIG += skip_target_version_ext 13 | 14 | #CONFIG += c++17 link_pkgconfig 15 | #PKGCONFIG += rofi 16 | 17 | CONFIG += c++17 18 | 19 | #ugly hack because qmake replaces -I/usb/include by -isystem /usr/include and breaks g++ 20 | QMAKE_CXXFLAGS += $$system("pkgconf --cflags-only-I rofi") 21 | 22 | #I just need the headers, no need to link agains QtGUI 23 | QMAKE_CXXFLAGS += $$system("pkgconf --cflags-only-I Qt5Gui") 24 | 25 | # The following define makes your compiler emit warnings if you use 26 | # any Qt feature that has been marked deprecated (the exact warnings 27 | # depend on your compiler). Please consult the documentation of the 28 | # deprecated API in order to know how to port your code away from it. 29 | DEFINES += QT_DEPRECATED_WARNINGS 30 | 31 | # You can also make your code fail to compile if it uses deprecated APIs. 32 | # In order to do so, uncomment the following line. 33 | # You can also select to disable deprecated APIs only up to a certain version of Qt. 34 | #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 35 | 36 | SOURCES += \ 37 | qlipmon_interface.cpp \ 38 | config.cpp \ 39 | qlipdata.cpp \ 40 | rofiqlipmon.cpp \ 41 | ../server/database_entry.cpp \ 42 | 43 | HEADERS += \ 44 | qlipmon_interface.h \ 45 | config.h \ 46 | qlipdata.h \ 47 | rofi-qlipmon_global.h \ 48 | 49 | # Default rules for deployment. 50 | unix { 51 | target.path = $${PREFIX}/lib/rofi/ 52 | } 53 | !isEmpty(target.path): INSTALLS += target 54 | -------------------------------------------------------------------------------- /rofi/rofiqlipmon.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | extern "C" { 4 | #include 5 | #include 6 | #include 7 | } 8 | 9 | #include "qlipdata.h" 10 | 11 | RofiData* RofiDataFromMode(const Mode *mode){ 12 | return reinterpret_cast (mode_get_private_data(mode)); 13 | } 14 | 15 | static char* QStringDupa(const QString& line){ 16 | QByteArray ba = line.toLocal8Bit(); 17 | return g_strdup(ba.data()); 18 | } 19 | 20 | 21 | /** 22 | * @param mode The mode to initialize 23 | * 24 | * Initialize mode 25 | * 26 | * @returns FALSE if there was a failure, TRUE if successful 27 | */ 28 | static int qlipmon_mode_init(Mode *sw) { 29 | if (mode_get_private_data(sw) == nullptr) { 30 | RofiData *data = QlipData::getEntries(); 31 | mode_set_private_data(sw, reinterpret_cast(data)); 32 | } 33 | return TRUE; 34 | } 35 | 36 | /** 37 | * @param mode The mode to query 38 | * 39 | * Get the number of entries in the mode. 40 | * 41 | * @returns an unsigned in with the number of entries. 42 | */ 43 | static unsigned int qlipmon_mode_get_num_entries(const Mode *sw) { 44 | RofiData* data = RofiDataFromMode(sw); 45 | if (data->error){ 46 | return 0; 47 | }else{ 48 | return data->entries.size(); 49 | } 50 | } 51 | 52 | /** 53 | * @param mode The mode to query 54 | * @param menu_retv The menu return value. 55 | * @param input Pointer to the user input string. [in][out] 56 | * @param selected_line the line selected by the user. 57 | * 58 | * Acts on the user interaction. 59 | * 60 | * @returns the next #ModeMode. 61 | */ 62 | static ModeMode qlipmon_mode_result( 63 | Mode *sw, 64 | int mretv, 65 | char **input, 66 | unsigned int selected_line 67 | ) { 68 | ModeMode retv = MODE_EXIT; 69 | Q_UNUSED( sw ) 70 | Q_UNUSED( input ) 71 | 72 | RofiData* data = RofiDataFromMode(sw); 73 | 74 | 75 | if (mretv & MENU_NEXT) { 76 | retv = NEXT_DIALOG; 77 | } else if (mretv & MENU_PREVIOUS) { 78 | retv = PREVIOUS_DIALOG; 79 | } else if (mretv & MENU_QUICK_SWITCH) { 80 | retv = (ModeMode) (mretv & MENU_LOWER_MASK); 81 | } else if ((mretv & MENU_OK) ) { 82 | if(!data->error){ 83 | const QString selected = data->entries.value(selected_line); 84 | qDebug()<<"Selected = " << selected; 85 | QlipData::setText(selected); 86 | } 87 | retv = MODE_EXIT; 88 | } else if ((mretv & MENU_ENTRY_DELETE) == MENU_ENTRY_DELETE) { 89 | retv = RELOAD_DIALOG; 90 | } 91 | return retv; 92 | } 93 | 94 | /** 95 | * @param mode The mode to destroy 96 | * 97 | * Destroy the mode 98 | */ 99 | static void qlipmon_mode_destroy(Mode *sw) { 100 | RofiData* data = RofiDataFromMode(sw); 101 | if (data != nullptr) { 102 | delete data; 103 | } 104 | } 105 | 106 | /** 107 | * @param mode The mode to query 108 | * 109 | * Query the mode for a user display. 110 | * 111 | * @return a new allocated (valid pango markup) message to display (user should 112 | * free). 113 | */ 114 | static char *qlipmon_get_message(const Mode *sw) { 115 | RofiData* data = RofiDataFromMode(sw); 116 | if(data->error){ 117 | QString line = QString("QlipMon Error: "); 118 | line += QString("") + data->errorString + QString(""); 119 | return QStringDupa(line); 120 | }else if( 0 == data->entries.size() ){ 121 | QString line = QString("QlipMon: No clipboard history!"); 122 | return QStringDupa(line); 123 | }else{ 124 | return NULL; 125 | } 126 | } 127 | 128 | /** 129 | * @param mode The mode to query 130 | * @param selected_line The entry to query 131 | * @param state The state of the entry [out] 132 | * @param attribute_list List of extra (pango) attribute to apply when displaying. [out][null] 133 | * @param get_entry If the should be returned. 134 | * 135 | * Returns the string as it should be displayed for the entry and the state of how it should be displayed. 136 | * 137 | * @returns allocated new string and state when get_entry is TRUE otherwise just the state. 138 | */ 139 | static char *get_display_value( 140 | const Mode *sw, 141 | unsigned int selected_line, 142 | int *state, 143 | GList **attr_list, 144 | int get_entry 145 | ) { 146 | Q_UNUSED( get_entry ) 147 | Q_UNUSED( state ) 148 | Q_UNUSED( attr_list ) 149 | RofiData* data = RofiDataFromMode(sw); 150 | 151 | // Rofi is not yet exporting these constants in their headers 152 | // *state |= MARKUP; 153 | // https://github.com/DaveDavenport/rofi/blob/79adae77d72be3de96d1c4e6d53b6bae4cb7e00e/include/widgets/textbox.h#L104 154 | //*state |= 8; 155 | 156 | if(data->error){ 157 | return NULL; 158 | }else{ 159 | QString line = data->entries.value(selected_line); 160 | line.replace("\n", "⏎"); 161 | line.replace("\t", "⭾"); 162 | return QStringDupa(line); 163 | } 164 | 165 | } 166 | 167 | /** 168 | * @param sw The mode object. 169 | * @param tokens The tokens to match against. 170 | * @param index The index in this plugin to match against. 171 | * 172 | * Match the entry. 173 | * 174 | * @param returns try when a match. 175 | */ 176 | static int qlipmon_token_match(const Mode *sw, rofi_int_matcher **tokens, unsigned int index) { 177 | RofiData* data = RofiDataFromMode(sw); 178 | 179 | if(data->error){ 180 | // always display error message. 181 | // Mute since now no message is displayed in the ellement list 182 | // error message is displayed in the status bar 183 | return TRUE; 184 | }else{ 185 | QByteArray ba = data->entries.value(index).toLocal8Bit(); 186 | return helper_token_match(tokens, ba.data()); 187 | } 188 | } 189 | 190 | cairo_surface_t * qlipmon_get_icon ( const Mode *mode, unsigned int selected_line, int height ){ 191 | Q_UNUSED( mode ) 192 | Q_UNUSED( selected_line ) 193 | Q_UNUSED( height ) 194 | return nullptr; 195 | } 196 | 197 | static char _name[] = "qlipmon"; 198 | 199 | G_MODULE_EXPORT Mode mode = { 200 | .abi_version = ABI_VERSION, 201 | .name = _name, 202 | .cfg_name_key = {}, 203 | //uncommenting this results in segfault 204 | .display_name = {}, 205 | ._init = qlipmon_mode_init, 206 | ._destroy = qlipmon_mode_destroy, 207 | ._get_num_entries = qlipmon_mode_get_num_entries, 208 | ._result = qlipmon_mode_result, 209 | ._token_match = qlipmon_token_match, 210 | ._get_display_value = get_display_value, 211 | ._get_icon = nullptr, 212 | ._get_completion = nullptr, 213 | ._preprocess_input = nullptr, 214 | ._get_message = qlipmon_get_message, 215 | .private_data = nullptr, 216 | .free = nullptr, 217 | .ed = {}, 218 | .module = {}, 219 | }; 220 | -------------------------------------------------------------------------------- /server/config.cpp: -------------------------------------------------------------------------------- 1 | #include "config.h" 2 | #include 3 | #include 4 | 5 | static QSettings getSettings(){ 6 | //QSettings settings(path, QSettings::Format::NativeFormat); 7 | return QSettings("qlipmon", "server"); 8 | } 9 | 10 | void Config::loadArgs(const QStringList &args){ 11 | load(); // load values from config 12 | 13 | QCommandLineParser parser; 14 | parser.setApplicationDescription("Monitors clipboard history"); 15 | parser.addHelpOption(); 16 | parser.addVersionOption(); 17 | 18 | QCommandLineOption historyNumberOption(QStringList() << "n" << "limit-history"); 19 | historyNumberOption.setDescription(QString("maximum number of elements saved (0 is no history, negative is infinite, default is ")+QString::number(numberEntries)+")."); 20 | historyNumberOption.setValueName("number of elements"); 21 | historyNumberOption.setDefaultValue(QString::number(numberEntries)); 22 | parser.addOption(historyNumberOption); 23 | 24 | QCommandLineOption dbusOption(QStringList() << "d" << "dbus"); 25 | dbusOption.setDescription("enable dbus"); 26 | dbusOption.setValueName("true/false"); 27 | dbusOption.setDefaultValue("true"); 28 | parser.addOption(dbusOption); 29 | 30 | QCommandLineOption broadcastOption(QStringList() << "b" << "broadcast"); 31 | broadcastOption.setDescription("broadcast new selection via dbus even"); 32 | broadcastOption.setValueName("true/false"); 33 | broadcastOption.setDefaultValue("false"); 34 | parser.addOption(broadcastOption); 35 | 36 | QCommandLineOption saveOption(QStringList() << "s" << "save config"); 37 | saveOption.setDescription("save command line values in config file"); 38 | parser.addOption(saveOption); 39 | 40 | parser.process(args); 41 | 42 | const long _history_number = parser.value(historyNumberOption).toLong(); 43 | 44 | if (parser.isSet(broadcastOption)) 45 | broadcast = parser.value(broadcastOption).toLower() == "true"; 46 | 47 | if (parser.isSet(dbusOption)) 48 | dbus = parser.value(dbusOption).toLower() == "true"; 49 | 50 | numberEntries = _history_number; 51 | qDebug()<<"Config after command line parsing = "<<*this; 52 | 53 | if (parser.isSet(saveOption)) 54 | save(); 55 | } 56 | 57 | void Config::loadArgs(int argc, char* argv[]){ 58 | const QStringList args(argv, argv + argc); 59 | loadArgs(args); 60 | } 61 | 62 | void Config::load(){ 63 | QSettings settings = getSettings(); 64 | qDebug()<<"loading settings from file "< 5 | 6 | class Config{ 7 | public: 8 | int numberEntries = 500; 9 | bool broadcast = true; 10 | bool dbus = true; 11 | 12 | //void load(const QString& path); 13 | void load(); 14 | //void save(const QString& path); 15 | void save(); 16 | 17 | void loadArgs(const QStringList &args); 18 | void loadArgs(int argc, char* argv[]); 19 | }; 20 | 21 | QDebug &operator<<(QDebug &out, const Config &c); 22 | 23 | 24 | #endif // CONFIG_H 25 | -------------------------------------------------------------------------------- /server/database.cpp: -------------------------------------------------------------------------------- 1 | #include "database.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | 13 | static QMutex __mtx; 14 | static QAtomicInt __count(0); 15 | 16 | /* 17 | TODO 18 | limit number of entries or space 19 | deal with duplicate entries better 20 | TTS ? 21 | */ 22 | 23 | 24 | static QSqlDatabase __database(){ 25 | return QSqlDatabase::database(); 26 | } 27 | 28 | static bool __transaction(){ 29 | return __database().transaction(); 30 | } 31 | 32 | static bool __commit(){ 33 | return __database().commit(); 34 | } 35 | 36 | static bool __rollback(){ 37 | return __database().rollback(); 38 | } 39 | 40 | static database_entry entryFromQuery(QSqlQuery& query){ 41 | database_entry ret; 42 | ret.id = query.value(0).toULongLong(); 43 | ret.text = query.value(1).toString(); 44 | ret.mode = QClipboard::Mode(query.value(2).toInt()); 45 | ret.when = QDateTime::fromMSecsSinceEpoch(query.value(3).toLongLong()); 46 | 47 | return ret; 48 | } 49 | 50 | static QList entriesFromSQL(const QString &sql){ 51 | QSqlQuery query(sql); 52 | query.exec(); 53 | 54 | if(!query.isActive()) 55 | qWarning() << "SQL SELECT ERROR: " << query.lastError().text(); 56 | 57 | QList ret; 58 | 59 | while(query.next()){ 60 | ret.append(entryFromQuery(query)); 61 | } 62 | 63 | return ret; 64 | 65 | } 66 | 67 | database::~database(){ 68 | qDebug()<<"~database"; 69 | QMutexLocker locker(&__mtx); 70 | 71 | if(0==--__count){ 72 | qDebug()<<"Closing database"; 73 | QSqlDatabase db = __database(); 74 | db.close(); 75 | 76 | if(db.open()){ 77 | qCritical() << db.lastError(); 78 | return; 79 | } 80 | } 81 | 82 | } 83 | 84 | database::database(const int _numberEntries){ 85 | numberEntries = _numberEntries; 86 | QMutexLocker locker(&__mtx); 87 | 88 | if (__count){ 89 | ++__count; 90 | qDebug()<<"Database already initalized [count = "<<__count<<"]";; 91 | return; 92 | } 93 | 94 | qDebug() << "Available QtSQL drivers:" << QSqlDatabase::drivers(); 95 | const QString DRIVER("QSQLITE"); 96 | QSqlDatabase db = QSqlDatabase::addDatabase(DRIVER); 97 | db.setDatabaseName(":memory:"); 98 | 99 | if(!db.open()){ 100 | qCritical() << db.lastError(); 101 | return; 102 | } 103 | 104 | const QStringList DDLs ={ 105 | "CREATE TABLE texts (" 106 | "id INTEGER PRIMARY KEY AUTOINCREMENT," 107 | "text TEXT NOT NULL" 108 | ");", 109 | "CREATE TABLE pastes (" 110 | "id INTEGER PRIMARY KEY AUTOINCREMENT," 111 | "text_id INTEGER REFERENCES texts(id) ON DELETE CASCADE," 112 | "mode INTEGER NOT NULL," 113 | "ts INTEGER DEFAULT NULL" 114 | ");", 115 | "CREATE UNIQUE INDEX idx_texts_text ON texts(text);" 116 | }; 117 | 118 | for (const QString& DDL: DDLs){ 119 | QSqlQuery query(DDL); 120 | if(!query.isActive()){ 121 | qWarning() << "SQL CREATE ERROR: " << query.lastError().text(); 122 | return; 123 | } 124 | } 125 | 126 | ++__count;; 127 | 128 | } 129 | 130 | void database::_save(QString text, QClipboard::Mode mode){ 131 | qDebug()<<"save("< database::getDuplicateEntries(){ 241 | return entriesFromSQL("SELECT pastes.id as id, text, mode, ts FROM pastes JOIN texts ON (pastes.text_id=texts.id) ORDER BY id DESC"); 242 | } 243 | 244 | QList database::getUniqueEntries(){ 245 | return entriesFromSQL( 246 | "SELECT pastes.id as id, text, mode, ts FROM pastes " 247 | "JOIN texts ON (pastes.text_id=texts.id) " 248 | "WHERE pastes.id IN (" 249 | "SELECT MAX(id) FROM pastes GROUP BY (text_id)" 250 | ")" 251 | "ORDER BY id DESC;" 252 | ); 253 | } 254 | 255 | 256 | void database::__cleanup(){ 257 | qDebug()<<"Cleaning up for "< 5 | #include 6 | #include 7 | #include 8 | 9 | #include "database_entry.h" 10 | 11 | 12 | class database 13 | { 14 | public: 15 | 16 | database(const int numberEntries=500); 17 | ~database(); 18 | 19 | database_entry getLast(); 20 | QList getUniqueEntries(); 21 | QList getDuplicateEntries(); 22 | QString getLast(QClipboard::Mode mode); 23 | 24 | 25 | protected: 26 | void _save(QString text, QClipboard::Mode mode); 27 | 28 | private: 29 | int numberEntries; 30 | void __cleanup(); 31 | 32 | }; 33 | 34 | #endif // DATABASE_H 35 | -------------------------------------------------------------------------------- /server/database_entry.cpp: -------------------------------------------------------------------------------- 1 | #include "database_entry.h" 2 | #include 3 | #include 4 | 5 | 6 | /// Initializer 7 | class Init { 8 | public: 9 | Init() { 10 | qDebug()<<"Registering types"; 11 | qRegisterMetaType("database_entry"); 12 | qDBusRegisterMetaType(); 13 | 14 | qRegisterMetaType>("list_database_entry"); 15 | qDBusRegisterMetaType>(); 16 | qDebug()<<"Types registered"; 17 | } 18 | }; 19 | static Init _initTypes; 20 | 21 | QDBusArgument &operator<<(QDBusArgument &argument, const database_entry &e ){ 22 | argument.beginStructure(); 23 | argument << e.id; 24 | argument << e.text; 25 | argument << e.mode; 26 | argument << e.when.toMSecsSinceEpoch(); 27 | argument.endStructure(); 28 | return argument; 29 | } 30 | 31 | const QDBusArgument &operator>>(const QDBusArgument &argument, database_entry &e ) 32 | { 33 | qint64 when; 34 | int mode; 35 | 36 | argument.beginStructure(); 37 | argument >> e.id; 38 | argument >> e.text; 39 | argument >> mode; 40 | argument >> when; 41 | argument.endStructure(); 42 | 43 | e.mode = QClipboard::Mode(mode); 44 | e.when = QDateTime::fromMSecsSinceEpoch(when); 45 | return argument; 46 | } 47 | 48 | QDebug &operator<<(QDebug &out, const database_entry &e){ 49 | out<<"database::entry{ id:"< 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | 14 | 15 | struct database_entry{ 16 | quint64 id; 17 | QString text; 18 | QClipboard::Mode mode; 19 | QDateTime when; 20 | }; 21 | 22 | QDebug &operator<<(QDebug &out, const database_entry &entry); 23 | const QDBusArgument &operator>>(const QDBusArgument &argument, database_entry &e ); 24 | 25 | Q_DECLARE_METATYPE( database_entry ); 26 | Q_DECLARE_METATYPE( QList ); 27 | 28 | #endif // DATABASE_ENTRY_H 29 | -------------------------------------------------------------------------------- /server/dbus.cpp: -------------------------------------------------------------------------------- 1 | #include "dbus.h" 2 | 3 | Dbus::Dbus() 4 | { 5 | 6 | } 7 | -------------------------------------------------------------------------------- /server/dbus.h: -------------------------------------------------------------------------------- 1 | #ifndef DBUS_H 2 | #define DBUS_H 3 | 4 | #include 5 | 6 | class Dbus 7 | { 8 | public: 9 | Dbus(); 10 | }; 11 | 12 | #endif // DBUS_H 13 | -------------------------------------------------------------------------------- /server/main.cpp: -------------------------------------------------------------------------------- 1 | #include "qlipmon.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | class Application: public QApplication { 8 | private: 9 | QlipMon *qlipmon; 10 | 11 | public: 12 | Application(int argc, char* argv[]): 13 | QApplication(argc, argv), 14 | qlipmon(nullptr) 15 | { 16 | 17 | setApplicationName("QlipMon"); 18 | setApplicationVersion("1.0"); 19 | 20 | Config config; 21 | config.loadArgs(argc, argv); 22 | qlipmon = new QlipMon(config); 23 | 24 | 25 | } 26 | 27 | ~Application(){ 28 | if (qlipmon != nullptr) 29 | delete qlipmon; 30 | } 31 | }; 32 | 33 | int main(int argc, char *argv[]) 34 | { 35 | Application app(argc, argv); 36 | return app.exec(); 37 | } 38 | -------------------------------------------------------------------------------- /server/qlipmon-server.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Qlipmon clipboard manager for user %u 3 | After=graphical-session.target 4 | 5 | [Service] 6 | ExecStart=qlipmon-server 7 | Type=simple 8 | After=graphical-session.target 9 | Restart=on-failure 10 | RestartSec=5 11 | StartLimitInterval=60s 12 | StartLimitBurst=3 13 | 14 | [Install] 15 | WantedBy=default.target 16 | -------------------------------------------------------------------------------- /server/qlipmon.cpp: -------------------------------------------------------------------------------- 1 | #include "qlipmon.h" 2 | #include "config.h" 3 | #include "qlipmon_adaptor.h" 4 | 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | 13 | QlipMon::QlipMon(const Config& config, QObject *parent): QObject(parent), database(config.numberEntries){ 14 | QClipboard* clip = QGuiApplication::clipboard(); 15 | QObject::connect(clip, &QClipboard::changed, this, &QlipMon::changed); 16 | 17 | if(config.dbus){ 18 | new QlipmonAdaptor(this); 19 | QDBusConnection connection = QDBusConnection::sessionBus(); 20 | connection.registerObject(QLIPMON_DBUS_PATH, this); 21 | connection.registerService(QLIPMON_DBUS_FQDN); 22 | }else{ 23 | qWarning()<<"No DBUS interface. Why even run this?"; 24 | } 25 | 26 | setProperty("broadcast", config.broadcast); 27 | } 28 | 29 | void QlipMon::changed(QClipboard::Mode mode){ 30 | QString text = clip->text(mode); 31 | 32 | if(_broadcast){ 33 | qDebug()<<"broadcasting"; 34 | emit updated(text, (int)(mode)); 35 | }else{ 36 | qDebug()<<"NOT broadcasting"; 37 | } 38 | 39 | _save(text, mode); 40 | } 41 | 42 | QString QlipMon::getLastText(int mode){ 43 | return database::getLast(QClipboard::Mode(mode)); 44 | } 45 | 46 | QStringList QlipMon::getTextHistory(int _mode, bool duplicates){ 47 | const auto mode = QClipboard::Mode(_mode); 48 | QStringList ret; 49 | 50 | auto entries = duplicates ? database::getDuplicateEntries(): database::getUniqueEntries(); 51 | 52 | for (const database_entry &entry : entries){ 53 | if(_mode < 0 || entry.mode == mode){ 54 | ret.append(entry.text); 55 | } 56 | } 57 | return ret; 58 | } 59 | 60 | QList QlipMon::getHistory(){ 61 | return database::getUniqueEntries(); 62 | } 63 | 64 | void QlipMon::setText(const QString &text, int mode){ 65 | qDebug()<<"setText("<setText(text, QClipboard::Mode::Clipboard); 70 | clip->setText(text, QClipboard::Mode::Selection); 71 | }else{ 72 | clip->setText(text, QClipboard::Mode(mode)); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /server/qlipmon.h: -------------------------------------------------------------------------------- 1 | #ifndef QLIPMON_H 2 | #define QLIPMON_H 3 | 4 | #include "database.h" 5 | #include "database_entry.h" 6 | #include "config.h" 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | class QlipMon : public QObject, public database 15 | { 16 | Q_OBJECT 17 | //Q_CLASSINFO("D-Bus Interface", QLIPMON_DBUS_FQDN) 18 | Q_PROPERTY(bool broadcast MEMBER _broadcast) 19 | 20 | public: 21 | //explicit QlipMon(QObject *parent = nullptr); 22 | explicit QlipMon(const Config& config, QObject *parent = nullptr); 23 | 24 | public slots: 25 | void setText(const QString &text, int mode=0); 26 | QString getLastText(int mode=0); 27 | QStringList getTextHistory(int mode=-1, bool duplicates=false); 28 | QList getHistory(); 29 | 30 | 31 | signals: 32 | void updated(QString text, int mode); 33 | 34 | private: 35 | QClipboard* clip; 36 | bool _broadcast; 37 | 38 | private slots: 39 | void changed(QClipboard::Mode mode); 40 | 41 | }; 42 | 43 | 44 | #endif // QLIPMON_H 45 | -------------------------------------------------------------------------------- /server/qlipmon_adaptor.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This file was generated by qdbusxml2cpp version 0.8 3 | * Command line was: qdbusxml2cpp -V -c QlipmonAdaptor -i qlipmon_adaptor.h -a :qlipmon_adaptor.cpp ../qlipmon.xml 4 | * 5 | * qdbusxml2cpp is Copyright (C) 2020 The Qt Company Ltd. 6 | * 7 | * This is an auto-generated file. 8 | * Do not edit! All changes made to it will be lost. 9 | */ 10 | 11 | #include "qlipmon_adaptor.h" 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | /* 21 | * Implementation of adaptor class QlipmonAdaptor 22 | */ 23 | 24 | QlipmonAdaptor::QlipmonAdaptor(QObject *parent) 25 | : QDBusAbstractAdaptor(parent) 26 | { 27 | // constructor 28 | setAutoRelaySignals(true); 29 | } 30 | 31 | QlipmonAdaptor::~QlipmonAdaptor() 32 | { 33 | // destructor 34 | } 35 | 36 | bool QlipmonAdaptor::broadcast() const 37 | { 38 | // get the value of property broadcast 39 | return qvariant_cast< bool >(parent()->property("broadcast")); 40 | } 41 | 42 | void QlipmonAdaptor::setBroadcast(bool value) 43 | { 44 | // set the value of property broadcast 45 | parent()->setProperty("broadcast", QVariant::fromValue(value)); 46 | } 47 | 48 | QList QlipmonAdaptor::getHistory() 49 | { 50 | // handle method call DOMAIN.DOMAIN.getHistory 51 | QList out0; 52 | QMetaObject::invokeMethod(parent(), "getHistory", Q_RETURN_ARG(QList, out0)); 53 | return out0; 54 | } 55 | 56 | QString QlipmonAdaptor::getLastText() 57 | { 58 | // handle method call DOMAIN.DOMAIN.getLastText 59 | QString out0; 60 | QMetaObject::invokeMethod(parent(), "getLastText", Q_RETURN_ARG(QString, out0)); 61 | return out0; 62 | } 63 | 64 | QString QlipmonAdaptor::getLastText(int mode) 65 | { 66 | // handle method call DOMAIN.DOMAIN.getLastText 67 | QString out0; 68 | QMetaObject::invokeMethod(parent(), "getLastText", Q_RETURN_ARG(QString, out0), Q_ARG(int, mode)); 69 | return out0; 70 | } 71 | 72 | QStringList QlipmonAdaptor::getTextHistory() 73 | { 74 | // handle method call DOMAIN.DOMAIN.getTextHistory 75 | QStringList out0; 76 | QMetaObject::invokeMethod(parent(), "getTextHistory", Q_RETURN_ARG(QStringList, out0)); 77 | return out0; 78 | } 79 | 80 | QStringList QlipmonAdaptor::getTextHistory(int mode) 81 | { 82 | // handle method call DOMAIN.DOMAIN.getTextHistory 83 | QStringList out0; 84 | QMetaObject::invokeMethod(parent(), "getTextHistory", Q_RETURN_ARG(QStringList, out0), Q_ARG(int, mode)); 85 | return out0; 86 | } 87 | 88 | QStringList QlipmonAdaptor::getTextHistory(int mode, bool duplicates) 89 | { 90 | // handle method call DOMAIN.DOMAIN.getTextHistory 91 | QStringList out0; 92 | QMetaObject::invokeMethod(parent(), "getTextHistory", Q_RETURN_ARG(QStringList, out0), Q_ARG(int, mode), Q_ARG(bool, duplicates)); 93 | return out0; 94 | } 95 | 96 | void QlipmonAdaptor::setText(const QString &text) 97 | { 98 | // handle method call DOMAIN.DOMAIN.setText 99 | QMetaObject::invokeMethod(parent(), "setText", Q_ARG(QString, text)); 100 | } 101 | 102 | void QlipmonAdaptor::setText(const QString &text, int mode) 103 | { 104 | // handle method call DOMAIN.DOMAIN.setText 105 | QMetaObject::invokeMethod(parent(), "setText", Q_ARG(QString, text), Q_ARG(int, mode)); 106 | } 107 | 108 | -------------------------------------------------------------------------------- /server/qlipmon_adaptor.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file was generated by qdbusxml2cpp version 0.8 3 | * Command line was: qdbusxml2cpp -V -c QlipmonAdaptor -i database_entry.h -a qlipmon_adaptor.h: ../qlipmon.xml 4 | * 5 | * qdbusxml2cpp is Copyright (C) 2020 The Qt Company Ltd. 6 | * 7 | * This is an auto-generated file. 8 | * This file may have been hand-edited. Look for HAND-EDIT comments 9 | * before re-generating it. 10 | */ 11 | 12 | #ifndef QLIPMON_ADAPTOR_H 13 | #define QLIPMON_ADAPTOR_H 14 | 15 | #include 16 | #include 17 | #include "database_entry.h" 18 | QT_BEGIN_NAMESPACE 19 | class QByteArray; 20 | template class QList; 21 | template class QMap; 22 | class QString; 23 | class QStringList; 24 | class QVariant; 25 | QT_END_NAMESPACE 26 | 27 | /* 28 | * Adaptor class for interface QLIPMON_DBUS_FQDN 29 | */ 30 | class QlipmonAdaptor: public QDBusAbstractAdaptor 31 | { 32 | Q_OBJECT 33 | Q_CLASSINFO("D-Bus Interface", QLIPMON_DBUS_FQDN) 34 | Q_CLASSINFO("D-Bus Introspection", "" 35 | " \n" 36 | " \n" 37 | " \n" 38 | " \n" 39 | " \n" 40 | " \n" 41 | " \n" 42 | " \n" 43 | " \n" 44 | " \n" 45 | " \n" 46 | " \n" 47 | " \n" 48 | " \n" 49 | " \n" 50 | " \n" 51 | " \n" 52 | " \n" 53 | " \n" 54 | " \n" 55 | " \n" 56 | " \n" 57 | " \n" 58 | " \n" 59 | " \n" 60 | " \n" 61 | " \n" 62 | " \n" 63 | " \n" 64 | " \n" 65 | " \n" 66 | " \n" 67 | " \n" 68 | " \n" 69 | " \n" 70 | " \n" 71 | " \n" 72 | "") 73 | public: 74 | QlipmonAdaptor(QObject *parent); 75 | virtual ~QlipmonAdaptor(); 76 | 77 | public: // PROPERTIES 78 | Q_PROPERTY(bool broadcast READ broadcast WRITE setBroadcast) 79 | bool broadcast() const; 80 | void setBroadcast(bool value); 81 | 82 | public Q_SLOTS: // METHODS 83 | QList getHistory(); 84 | QString getLastText(); 85 | QString getLastText(int mode); 86 | QStringList getTextHistory(); 87 | QStringList getTextHistory(int mode); 88 | QStringList getTextHistory(int mode, bool duplicates); 89 | void setText(const QString &text); 90 | void setText(const QString &text, int mode); 91 | Q_SIGNALS: // SIGNALS 92 | void updated(const QString &text, int mode); 93 | }; 94 | 95 | #endif 96 | -------------------------------------------------------------------------------- /server/server.pro: -------------------------------------------------------------------------------- 1 | include(../common.pri) 2 | 3 | TARGET=qlipmon-server 4 | 5 | greaterThan(QT_MAJOR_VERSION, 4): QT += widgets 6 | QT += core gui sql dbus 7 | 8 | CONFIG += c++17 9 | 10 | # The following define makes your compiler emit warnings if you use 11 | # any Qt feature that has been marked deprecated (the exact warnings 12 | # depend on your compiler). Please consult the documentation of the 13 | # deprecated API in order to know how to port your code away from it. 14 | DEFINES += QT_DEPRECATED_WARNINGS 15 | 16 | # You can also make your code fail to compile if it uses deprecated APIs. 17 | # In order to do so, uncomment the following line. 18 | # You can also select to disable deprecated APIs only up to a certain version of Qt. 19 | #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 20 | 21 | SOURCES += \ 22 | qlipmon_adaptor.cpp \ 23 | config.cpp \ 24 | main.cpp \ 25 | qlipmon.cpp \ 26 | database.cpp \ 27 | database_entry.cpp \ 28 | 29 | HEADERS += \ 30 | qlipmon_adaptor.h \ 31 | config.h \ 32 | qlipmon.h \ 33 | database.h \ 34 | database_entry.h \ 35 | 36 | target.path = $${PREFIX}/bin 37 | INSTALLS += target 38 | 39 | systemd.path = $${PREFIX}/lib/systemd/user/ 40 | systemd.files += qlipmon-server.service 41 | 42 | INSTALLS += systemd 43 | -------------------------------------------------------------------------------- /xml2cpp.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -ex 2 | 3 | cd `dirname "$0"`; 4 | 5 | #PROCESSOR="qdbusxml2cpp -V -N" 6 | PROCESSOR="qdbusxml2cpp -V" 7 | 8 | function process_file(){ 9 | sed 's/\\"DOMAIN.DOMAIN\\"/\\"" QLIPMON_DBUS_FQDN "\\"/g' -i $1 10 | sed 's/\"DOMAIN.DOMAIN\"/QLIPMON_DBUS_FQDN/g' -i $1 11 | sed 's/DOMAIN.DOMAIN/QLIPMON_DBUS_FQDN/g' -i $1 12 | } 13 | 14 | cd server 15 | 16 | $PROCESSOR -c QlipmonAdaptor -i database_entry.h -a qlipmon_adaptor.h: ../qlipmon.xml 17 | $PROCESSOR -c QlipmonAdaptor -i qlipmon_adaptor.h -a :qlipmon_adaptor.cpp ../qlipmon.xml 18 | 19 | process_file qlipmon_adaptor.h; 20 | 21 | cd ../rofi 22 | $PROCESSOR -i ../server/database_entry.h --classname QlipMonInterface -p qlipmon_interface.h: ../qlipmon.xml 23 | $PROCESSOR -i qlipmon_interface.h --classname QlipMonInterface -p :qlipmon_interface.cpp ../qlipmon.xml 24 | 25 | process_file qlipmon_interface.h 26 | --------------------------------------------------------------------------------