PIVX Core  5.6.99
P2P Digital Currency
consolewidget.cpp
Go to the documentation of this file.
1 // Copyright (c) 2019-2021 The PIVX Core developers
2 // Distributed under the MIT software license, see the accompanying
3 // file COPYING or http://www.opensource.org/licenses/mit-license.php.
4 
6 #include "qt/settings/forms/ui_consolewidget.h"
7 
8 #include "qt/rpcexecutor.h"
9 #include "qtutils.h"
10 
11 #include "clientmodel.h"
12 
13 #include "chainparams.h"
14 #include "sapling/key_io_sapling.h"
15 #include "util/system.h"
16 #include "utilitydialog.h"
17 
18 #ifdef ENABLE_WALLET
19 #include "wallet/wallet.h"
20 #endif // ENABLE_WALLET
21 
22 #include <univalue.h>
23 
24 #ifdef ENABLE_WALLET
25 #include <db_cxx.h>
26 #endif
27 
28 #include <QDir>
29 #include <QGraphicsDropShadowEffect>
30 #include <QKeyEvent>
31 #include <QMenu>
32 #include <QScrollBar>
33 #include <QSignalMapper>
34 #include <QThread>
35 #include <QTime>
36 #include <QStringList>
37 
38 const int CONSOLE_HISTORY = 50;
39 
40 const struct {
41  const char* url;
42  const char* source;
43 } ICON_MAPPING[] = {
44  {"cmd-request", ":/icons/ic-transaction-received"},
45  {"cmd-reply", ":/icons/ic-transaction-sent"},
46  {"cmd-error", ":/icons/ic-transaction-sent"},
47  {"misc", ":/icons/ic-transaction-staked"},
48  {nullptr, nullptr}};
49 
51  PWidget(_window,parent),
52  ui(new Ui::SettingsConsoleWidget)
53 {
54  ui->setupUi(this);
55 
56  this->setStyleSheet(parent->styleSheet());
57 
58  // Containers
59  setCssProperty({ui->left, ui->messagesWidget}, "container");
60  ui->left->setContentsMargins(10,10,10,10);
61  ui->messagesWidget->setReadOnly(true);
62  ui->messagesWidget->setTextInteractionFlags(Qt::TextInteractionFlag::TextSelectableByMouse);
63 
64  // Title
65  setCssTitleScreen(ui->labelTitle);
66 
67  // Console container
68  setCssProperty(ui->consoleWidget, "container-square");
69  setShadow(ui->consoleWidget);
70 
71  // Edit
72  ui->lineEdit->setPlaceholderText(tr("Console input"));
73  initCssEditLine(ui->lineEdit);
74 
75  // Buttons
76  ui->pushButton->setProperty("cssClass", "ic-arrow");
77  setCssBtnSecondary(ui->pushButtonOpenDebug);
78  setCssBtnSecondary(ui->pushButtonClear);
79  setCssBtnSecondary(ui->pushButtonCommandOptions);
80 
81  setShadow(ui->pushButtonClear);
82  connect(ui->pushButtonClear, &QPushButton::clicked, [this]{ clear(false); });
83  connect(ui->pushButtonOpenDebug, &QPushButton::clicked, [this](){
84  if (!GUIUtil::openDebugLogfile()) {
85  inform(tr("Cannot open debug file.\nVerify that you have installed a predetermined text editor."));
86  }
87  });
88  connect(ui->pushButtonCommandOptions, &QPushButton::clicked, this, &SettingsConsoleWidget::onCommandsClicked);
89 
90  // Install event filter for up and down arrow
91  ui->lineEdit->installEventFilter(this);
92  ui->messagesWidget->installEventFilter(this);
93 
94  // Register RPC timer interface
95  rpcTimerInterface = new QtRPCTimerInterface();
96  // avoid accidentally overwriting an existing, non QTThread
97  // based timer interface
98  RPCSetTimerInterfaceIfUnset(rpcTimerInterface);
99 
100  startExecutor();
101  clear();
102 }
103 
105 {
106  GUIUtil::saveWindowGeometry("nRPCConsoleWindow", this);
107  Q_EMIT stopExecutor();
109  delete rpcTimerInterface;
110  delete ui;
111 }
112 
113 
114 bool SettingsConsoleWidget::eventFilter(QObject* obj, QEvent* event)
115 {
116  if (event->type() == QEvent::KeyPress) // Special key handling
117  {
118  QKeyEvent* keyevt = static_cast<QKeyEvent*>(event);
119  int key = keyevt->key();
120  Qt::KeyboardModifiers mod = keyevt->modifiers();
121  switch (key) {
122  case Qt::Key_Up:
123  if (obj == ui->lineEdit) {
124  browseHistory(-1);
125  return true;
126  }
127  break;
128  case Qt::Key_Down:
129  if (obj == ui->lineEdit) {
130  browseHistory(1);
131  return true;
132  }
133  break;
134  case Qt::Key_PageUp: /* pass paging keys to messages widget */
135  case Qt::Key_PageDown:
136  if (obj == ui->lineEdit) {
137  QApplication::postEvent(ui->messagesWidget, new QKeyEvent(*keyevt));
138  return true;
139  }
140  break;
141  case Qt::Key_Return:
142  case Qt::Key_Enter:
143  // forward these events to lineEdit
144  if (obj == autoCompleter->popup()) {
145  QApplication::postEvent(ui->lineEdit, new QKeyEvent(*keyevt));
146  return true;
147  }
148  break;
149  default:
150  // Typing in messages widget brings focus to line edit, and redirects key there
151  // Exclude most combinations and keys that emit no text, except paste shortcuts
152  if (obj == ui->messagesWidget && ((!mod && !keyevt->text().isEmpty() && key != Qt::Key_Tab) ||
153  ((mod & Qt::ControlModifier) && key == Qt::Key_V) ||
154  ((mod & Qt::ShiftModifier) && key == Qt::Key_Insert))) {
155  ui->lineEdit->setFocus();
156  QApplication::postEvent(ui->lineEdit, new QKeyEvent(*keyevt));
157  return true;
158  }
159  if (mod == Qt::ControlModifier && key == Qt::Key_L)
160  clear(false);
161  }
162  }
163  return QWidget::eventFilter(obj, event);
164 }
165 
167 {
168  if (clientModel){
169 
170  //Setup autocomplete and attach it
171  QStringList wordList;
172  std::vector<std::string> commandList = tableRPC.listCommands();
173  for (size_t i = 0; i < commandList.size(); ++i)
174  {
175  wordList << commandList[i].c_str();
176  wordList << ("help " + commandList[i]).c_str();
177  }
178 
179  wordList << "help-console";
180  wordList.sort();
181  autoCompleter = new QCompleter(wordList, this);
182  autoCompleter->setModelSorting(QCompleter::CaseSensitivelySortedModel);
183  ui->lineEdit->setCompleter(autoCompleter);
184 
185  // clear the lineEdit after activating from QCompleter
186  autoCompleter->popup()->installEventFilter(this);
187  }
188 }
189 
190 void SettingsConsoleWidget::showEvent(QShowEvent *event)
191 {
192  if (ui->lineEdit) ui->lineEdit->setFocus();
193 }
194 
195 void SettingsConsoleWidget::clear(bool clearHistory)
196 {
197  ui->messagesWidget->clear();
198  if (clearHistory) {
199  history.clear();
200  historyPtr = 0;
201  }
202  ui->lineEdit->clear();
203  ui->lineEdit->setFocus();
204 
205  // Add smoothly scaled icon images.
206  // (when using width/height on an img, Qt uses nearest instead of linear interpolation)
207  for (int i = 0; ICON_MAPPING[i].url; ++i) {
208  ui->messagesWidget->document()->addResource(
209  QTextDocument::ImageResource,
210  QUrl(ICON_MAPPING[i].url),
211  QImage(ICON_MAPPING[i].source));
212  }
213 
214  QString theme;
215  changeTheme(isLightTheme(), theme);
216 
217 #ifdef Q_OS_MAC
218  QString clsKey = "(⌘)-L";
219 #else
220  QString clsKey = "Ctrl-L";
221 #endif
222 
223  messageInternal(RPCExecutor::CMD_REPLY, (tr("Welcome to the PIVX RPC console.") + "<br>" +
224  tr("Use up and down arrows to navigate history, and %1 to clear screen.").arg("<b>"+clsKey+"</b>") + "<br>" +
225  tr("Type %1 for an overview of available commands.").arg("<b>help</b>") + "<br>" +
226  tr("For more information on using this console type %1.").arg("<b>help-console</b>") +
227  "<br><span class=\"secwarning\"><br>" +
228  tr("WARNING: Scammers have been active, telling users to type commands here, stealing their wallet contents. Do not use this console without fully understanding the ramifications of a command.") +
229  "</span>"),
230  true);
231 }
232 
233 void SettingsConsoleWidget::messageInternal(int category, const QString& message, bool html)
234 {
235  QTime time = QTime::currentTime();
236  QString timeString = time.toString();
237  QString out;
238  out += "<table><tr><td class=\"time\" width=\"65\">" + timeString + "</td>";
239  out += "<td class=\"icon\" width=\"32\"><img src=\"" + RPCExecutor::categoryClass(category) + "\"></td>";
240  out += "<td class=\"message " + RPCExecutor::categoryClass(category) + "\" valign=\"middle\">";
241  if (html)
242  out += message;
243  else
244  out += GUIUtil::HtmlEscape(message, true);
245  out += "</td></tr></table>";
246  ui->messagesWidget->append(out);
247 }
248 
249 static bool PotentiallyDangerousCommand(const QString& cmd)
250 {
251  if (cmd.size() >= 12 && cmd.leftRef(10) == "dumpwallet") {
252  // at least one char for filename
253  return true;
254  }
255  if (cmd.size() >= 13 && cmd.leftRef(11) == "dumpprivkey") {
256  return true;
257  }
258  if (cmd.size() >= 18 && cmd.leftRef(16) == "exportsaplingkey") {
259  return true;
260  }
261  return false;
262 }
263 
265 {
266  QString cmd = ui->lineEdit->text();
267  ui->lineEdit->clear();
268 
269  if (!cmd.isEmpty()) {
270 
271  // ask confirmation before sending potentially dangerous commands
272  if (PotentiallyDangerousCommand(cmd) &&
273  !ask("DANGER!", "Your coins will be STOLEN if you give\nthe info to anyone!\n\nAre you sure?\n")) {
274  return;
275  }
276 
278  Q_EMIT cmdCommandRequest(cmd);
279  // Remove command, if already in history
280  history.removeOne(cmd);
281  // Append command to history
282  history.append(cmd);
283  // Enforce maximum history size
284  while (history.size() > CONSOLE_HISTORY)
285  history.removeFirst();
286  // Set pointer to end of history
287  historyPtr = history.size();
288  // Scroll console view to end
289  scrollToEnd();
290  }
291 }
292 
293 
295 {
296  historyPtr += offset;
297  if (historyPtr < 0)
298  historyPtr = 0;
299  if (historyPtr > history.size())
300  historyPtr = history.size();
301  QString cmd;
302  if (historyPtr < history.size())
303  cmd = history.at(historyPtr);
304  ui->lineEdit->setText(cmd);
305 }
306 
308 {
309  QThread* thread = new QThread;
310  RPCExecutor* executor = new RPCExecutor();
311  executor->moveToThread(thread);
312 
313  // Replies from executor object must go to this object
314  connect(executor, &RPCExecutor::reply, this, &SettingsConsoleWidget::response);
315  // Requests from this object must go to executor
317 
318  // On stopExecutor signal
319  // - queue executor for deletion (in execution thread)
320  // - quit the Qt event loop in the execution thread
321  connect(this, &SettingsConsoleWidget::stopExecutor, executor, &RPCExecutor::deleteLater);
322  connect(this, &SettingsConsoleWidget::stopExecutor, thread, &QThread::quit);
323  // Queue the thread for deletion (in this thread) when it is finished
324  connect(thread, &QThread::finished, thread, &QThread::deleteLater);
325 
326  // Default implementation of QThread::run() simply spins up an event loop in the thread,
327  // which is what we want.
328  thread->start();
329 }
330 
332 {
333  QScrollBar* scrollbar = ui->messagesWidget->verticalScrollBar();
334  scrollbar->setValue(scrollbar->maximum());
335 }
336 
337 
339 {
340  // Set default style sheet
341  if (isLightTheme) {
342  ui->messagesWidget->document()->setDefaultStyleSheet(
343  "table { color: #707070; }"
344  "td.time { color: #808080; padding-top: 3px; } "
345  "td.message { color: #707070;font-family: Courier, Courier New, Lucida Console, monospace; font-size: 12px; } " // Todo: Remove fixed font-size
346  "td.cmd-request { color: #006060; } "
347  "td.cmd-error { color: red; } "
348  ".secwarning { color: red; }"
349  "b { color: #707070; } ");
350  } else {
351  ui->messagesWidget->document()->setDefaultStyleSheet(
352  "table { color: #FFFFFF; }"
353  "td.time { color: #808080; padding-top: 3px; } "
354  "td.message { color: #FFFFFF;font-family: Courier, Courier New, Lucida Console, monospace; font-size: 12px; } " // Todo: Remove fixed font-size
355  "td.cmd-request { color: #006060; } "
356  "td.cmd-error { color: red; } "
357  ".secwarning { color: red; }"
358  "b { color: #FFFFFF; } ");
359  }
360  updateStyle(ui->messagesWidget);
361 }
362 
364 {
365  if (!clientModel)
366  return;
367 
368  HelpMessageDialog dlg(this, false);
369  dlg.exec();
370 
371 }
std::vector< std::string > listCommands() const
Returns a list of registered commands.
Definition: server.cpp:520
"Help message" dialog box
Definition: utilitydialog.h:22
PIVX GUI main class.
Definition: pivxgui.h:46
ClientModel * clientModel
Definition: pwidget.h:60
void message(const QString &title, const QString &body, unsigned int style, bool *ret=nullptr)
bool ask(const QString &title, const QString &message)
Definition: pwidget.cpp:55
void reply(int category, const QString &command)
void request(const QString &command)
Definition: rpcexecutor.cpp:35
static QString categoryClass(int category)
Definition: rpcexecutor.cpp:18
Ui::SettingsConsoleWidget * ui
Definition: consolewidget.h:59
void messageInternal(int category, const QString &message, bool html=false)
RPCTimerInterface * rpcTimerInterface
Definition: consolewidget.h:63
SettingsConsoleWidget(PIVXGUI *_window, QWidget *parent=nullptr)
void clear(bool clearHistory=true)
QCompleter * autoCompleter
Definition: consolewidget.h:64
void loadClientModel() override
void cmdCommandRequest(const QString &command)
void changeTheme(bool isLightTheme, QString &theme) override
virtual bool eventFilter(QObject *obj, QEvent *event) override
void scrollToEnd()
Scroll console view to end.
void browseHistory(int offset)
Go forward or back in history.
void showEvent(QShowEvent *event) override
void response(int category, const QString &message)
Definition: consolewidget.h:39
const struct @25 ICON_MAPPING[]
const int CONSOLE_HISTORY
const char * url
const char * source
QString HtmlEscape(const QString &str, bool fMultiLine)
Definition: guiutil.cpp:251
void saveWindowGeometry(const QString &strSetting, QWidget *parent)
Save window size and position.
Definition: guiutil.cpp:606
bool isLightTheme()
Definition: qtutils.cpp:210
void setCssTitleScreen(QLabel *label)
Definition: qtutils.cpp:324
void initCssEditLine(QLineEdit *edit, bool isDialog)
Definition: qtutils.cpp:272
void setShadow(QWidget *edit)
Definition: qtutils.cpp:292
void setCssProperty(std::initializer_list< QWidget * > args, const QString &value)
Definition: qtutils.cpp:334
void setCssBtnSecondary(QPushButton *btn, bool forceUpdate)
Definition: qtutils.cpp:307
void updateStyle(QWidget *widget)
Definition: qtutils.cpp:225
void RPCSetTimerInterfaceIfUnset(RPCTimerInterface *iface)
Set factory function for timers, but only if unset.
Definition: server.cpp:539
void RPCUnsetTimerInterface(RPCTimerInterface *iface)
Unset factory function for timers.
Definition: server.cpp:550
CRPCTable tableRPC
Definition: server.cpp:565