Skip to content

Commit 199ff25

Browse files
authored
Windows: Global "take screenshot" shortcut (flameshot-org#4556)
* Enable QHotkey on Windows to define global shortcut for taking screenshot * Check if Windows forces PrntScr for its snipping tool; provide the option to change corresponding registry key by Flameshot * Option to register Flameshot as MS-SCREENCLIP application * clang-format * Default shortcut * Minor: Move MsScreenclip initialization to separate function * Minor: Move MsScreenclip initialization to separate function
1 parent 5cd6269 commit 199ff25

15 files changed

Lines changed: 303 additions & 36 deletions

CMakeLists.txt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ set(QT_DEFAULT_MAJOR_VERSION 6 CACHE STRING "")
2020
set(QT_VERSION_MAJOR 6 CACHE STRING "")
2121
#BUILD_SHARED_LIBS used by QHotkey and QtColorWidgets
2222
option(BUILD_SHARED_LIBS OFF)
23+
#QHOTKEY_INSTALL used by QHotkey; must be disabled on Windows
24+
if(WIN32)
25+
set(QHOTKEY_INSTALL OFF CACHE BOOL "qHotkey install")
26+
endif()
2327

2428
#Needed due to linker error with QtColorWidget
2529
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
@@ -134,7 +138,7 @@ endif()
134138
# ToDo: Check if this is used anywhere
135139
option(BUILD_STATIC_LIBS ON)
136140

137-
if(APPLE)
141+
if(WIN32 OR APPLE)
138142
FetchContent_Declare(
139143
qHotKey
140144
GIT_REPOSITORY https://github.com/flameshot-org/QHotkey

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -237,9 +237,9 @@ These shortcuts are available in GUI mode:
237237
238238
### Global
239239
240-
Flameshot uses <kbd>Print screen</kbd> (Windows) and <kbd>cmd</kbd>-<kbd>shift</kbd>-<kbd>x</kbd> (macOS) as default global hotkeys.
241-
242-
On Linux, Flameshot doesn't yet support <kbd>Prt Sc</kbd> out of the box, but with a bit of configuration you can set this up:
240+
- Windows: <kbd>Prt Sc</kbd> (fixed, cannot be changed) and <kbd>Win</kbd> + <kbd>Shift</kbd> + <kbd>X</kbd> (can be changed in the settings)
241+
- macOS: <kbd>cmd</kbd> + <kbd>Shift</kbd> + <kbd>X</kbd> (can be changed in the settings)
242+
- Linux: Flameshot doesn't yet support <kbd>Prt Sc</kbd> out of the box, but you can set this up with a bit of configuration:
243243

244244
#### On KDE Plasma desktop
245245

src/CMakeLists.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,11 @@ if (WIN32)
296296
if (NOT USE_OPENSSL)
297297
message(WARNING "OpenSSL is required to upload screenshots")
298298
endif ()
299+
300+
target_link_libraries(
301+
flameshot
302+
qhotkey
303+
)
299304
endif ()
300305

301306
# Choose default color palette (small or large)

src/config/setshortcutwidget.cpp

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include <QLabel>
99
#include <QLayout>
1010
#include <QPixmap>
11+
#include <QTimer>
1112

1213
SetShortcutDialog::SetShortcutDialog(QDialog* parent,
1314
const QString& shortcutName)
@@ -34,7 +35,7 @@ SetShortcutDialog::SetShortcutDialog(QDialog* parent,
3435
m_layout->addWidget(infoIcon);
3536

3637
QString msg = "";
37-
#if defined(Q_OS_MAC)
38+
#if defined(Q_OS_MACOS)
3839
msg = tr(
3940
"Press Esc to cancel or ⌘+Backspace to disable the keyboard shortcut.");
4041
#else
@@ -58,6 +59,15 @@ SetShortcutDialog::SetShortcutDialog(QDialog* parent,
5859
infoBottom->setMargin(10);
5960
infoBottom->setAlignment(Qt::AlignCenter);
6061
m_layout->addWidget(infoBottom);
62+
63+
// 0ms Delay: Event loop waits until after show(); widget fully initialized
64+
QTimer::singleShot(0, this, &SetShortcutDialog::startCapture);
65+
}
66+
67+
void SetShortcutDialog::startCapture()
68+
{
69+
grabKeyboard(); // Call AFTER show()!
70+
setFocus();
6171
}
6272

6373
const QKeySequence& SetShortcutDialog::shortcut()
@@ -67,16 +77,19 @@ const QKeySequence& SetShortcutDialog::shortcut()
6777

6878
void SetShortcutDialog::keyPressEvent(QKeyEvent* ke)
6979
{
70-
if (ke->modifiers() & Qt::ShiftModifier) {
80+
Qt::KeyboardModifiers mods = ke->modifiers();
81+
82+
if (mods & Qt::ShiftModifier) {
7183
m_modifier += "Shift+";
7284
}
73-
if (ke->modifiers() & Qt::ControlModifier) {
85+
if (mods & Qt::ControlModifier) {
7486
m_modifier += "Ctrl+";
7587
}
76-
if (ke->modifiers() & Qt::AltModifier) {
88+
if (mods & Qt::AltModifier) {
7789
m_modifier += "Alt+";
7890
}
79-
if (ke->modifiers() & Qt::MetaModifier) {
91+
// ke->key() == Qt::Key_Meta required on Windows to grab Win key
92+
if (ke->modifiers() & Qt::MetaModifier || ke->key() == Qt::Key_Meta) {
8093
m_modifier += "Meta+";
8194
}
8295

@@ -91,3 +104,15 @@ void SetShortcutDialog::keyReleaseEvent(QKeyEvent* event)
91104
}
92105
accept();
93106
}
107+
108+
void SetShortcutDialog::accept()
109+
{
110+
releaseKeyboard();
111+
QDialog::accept();
112+
}
113+
114+
void SetShortcutDialog::reject()
115+
{
116+
releaseKeyboard();
117+
QDialog::reject();
118+
}

src/config/setshortcutwidget.h

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,16 @@ class SetShortcutDialog : public QDialog
1919
const QKeySequence& shortcut();
2020

2121
public:
22-
void keyPressEvent(QKeyEvent*);
23-
void keyReleaseEvent(QKeyEvent* event);
22+
void keyPressEvent(QKeyEvent*) override;
23+
void keyReleaseEvent(QKeyEvent* event) override;
2424

25-
signals:
25+
private slots:
26+
void accept() override;
27+
void reject() override;
2628

2729
private:
30+
void startCapture();
31+
2832
QVBoxLayout* m_layout;
2933
QString m_modifier;
3034
QKeySequence m_ks;

src/config/shortcutswidget.cpp

Lines changed: 209 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,14 @@
77
#include "src/core/qguiappcurrentscreen.h"
88
#include "src/utils/globalvalues.h"
99
#include "toolfactory.h"
10+
#include <QCheckBox>
1011
#include <QCursor>
12+
#include <QDir>
1113
#include <QHeaderView>
1214
#include <QIcon>
1315
#include <QKeyEvent>
1416
#include <QLabel>
17+
#include <QMessageBox>
1518
#include <QRect>
1619
#include <QScreen>
1720
#include <QStringList>
@@ -34,11 +37,20 @@ ShortcutsWidget::ShortcutsWidget(QWidget* parent)
3437
m_layout = new QVBoxLayout(this);
3538
m_layout->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
3639

40+
#if defined(Q_OS_WIN)
41+
checkPrintScreenForcesSnipping();
42+
#endif
43+
3744
initInfoTable();
3845
connect(ConfigHandler::getInstance(),
3946
&ConfigHandler::fileChanged,
4047
this,
4148
&ShortcutsWidget::populateInfoTable);
49+
50+
#if defined(Q_OS_WIN)
51+
initMsScreenclipCheckbox();
52+
#endif
53+
4254
show();
4355
}
4456

@@ -202,13 +214,15 @@ void ShortcutsWidget::loadShortcuts()
202214
appendShortcut("SCREENSHOT_HISTORY", tr("Screenshot history"));
203215
#endif
204216
#elif defined(Q_OS_WIN)
205-
217+
if (this->isPrintScreenKeyForSnippingDisabled()) {
218+
m_shortcuts << (QStringList() << "" << QObject::tr("Capture screen")
219+
<< "Print Screen");
220+
}
221+
appendShortcut("TAKE_SCREENSHOT", tr("Capture screen"));
206222
#ifdef ENABLE_IMGUR
207223
m_shortcuts << (QStringList() << "" << QObject::tr("Screenshot history")
208224
<< "Shift+Print Screen");
209225
#endif
210-
m_shortcuts << (QStringList()
211-
<< "" << QObject::tr("Capture screen") << "Print Screen");
212226
#else
213227
// TODO - Linux doesn't support global shortcuts for (XServer and Wayland),
214228
// possibly it will be solved in the QHotKey library later. So it is
@@ -241,3 +255,195 @@ const QString& ShortcutsWidget::nativeOSHotKeyText(const QString& text)
241255
return m_res;
242256
}
243257
#endif
258+
259+
#if defined(Q_OS_WIN)
260+
void ShortcutsWidget::checkPrintScreenForcesSnipping()
261+
{
262+
if (!isPrintScreenKeyForSnippingDisabled() &&
263+
!ConfigHandler().ignorePrntScrForcesSnipping()) {
264+
QMessageBox msgBox;
265+
msgBox.setWindowTitle("Flameshot");
266+
msgBox.setIcon(QMessageBox::Question);
267+
msgBox.setText(tr("It seems, that Windows forces to open its screenshot"
268+
" tool when the 'Print Screen' key is pressed. Would "
269+
"you like to disable this so that Flameshot can use "
270+
"the 'Print Screen' key?") +
271+
"\n\n" +
272+
tr("Flameshot must be restarted for changes to take "
273+
"effect."));
274+
QPushButton* yesBtn = msgBox.addButton(QMessageBox::Yes);
275+
QPushButton* noBtn = msgBox.addButton(QMessageBox::No);
276+
QPushButton* noDontAskAgainBtn =
277+
new QPushButton(tr("No, don't ask again"));
278+
msgBox.addButton(noDontAskAgainBtn, QMessageBox::RejectRole);
279+
msgBox.setDefaultButton(yesBtn);
280+
msgBox.exec();
281+
282+
if (msgBox.clickedButton() == yesBtn) {
283+
if (!disablePrintScreenKeyForSnipping()) {
284+
QMessageBox::warning(
285+
this, "Flameshot", tr("The registry could not be changed!"));
286+
}
287+
} else if (msgBox.clickedButton() == noDontAskAgainBtn) {
288+
ConfigHandler().setIgnorePrntScrForcesSnipping(true);
289+
}
290+
}
291+
}
292+
293+
bool ShortcutsWidget::isPrintScreenKeyForSnippingDisabled()
294+
{
295+
QSettings PrintKeyForSnipping("HKEY_CURRENT_USER\\Control Panel\\Keyboard",
296+
QSettings::NativeFormat);
297+
return PrintKeyForSnipping.value("PrintScreenKeyForSnippingEnabled", 1)
298+
.toInt() == 0;
299+
}
300+
301+
bool ShortcutsWidget::disablePrintScreenKeyForSnipping()
302+
{
303+
QSettings PrintKeyForSnipping("HKEY_CURRENT_USER\\Control Panel\\Keyboard",
304+
QSettings::NativeFormat);
305+
PrintKeyForSnipping.setValue("PrintScreenKeyForSnippingEnabled", 0);
306+
PrintKeyForSnipping.sync();
307+
if (QSettings::AccessError == PrintKeyForSnipping.status()) {
308+
return false;
309+
}
310+
return this->isPrintScreenKeyForSnippingDisabled();
311+
}
312+
313+
void ShortcutsWidget::initMsScreenclipCheckbox()
314+
{
315+
m_registerMsScreenclip =
316+
new QCheckBox(tr("Register Flameshot as MS-SCREENCLIP application "
317+
"(administrator privileges required)"),
318+
this);
319+
m_registerMsScreenclip->setToolTip(
320+
tr("After registering, you can select Flameshot as the default "
321+
"screenshot application in Windows Settings."));
322+
m_registerMsScreenclip->setChecked(isMsScreenclipRegistered());
323+
m_layout->addWidget(m_registerMsScreenclip);
324+
325+
connect(
326+
m_registerMsScreenclip, &QCheckBox::clicked, this, [this](bool checked) {
327+
if (checked) {
328+
if (!registerMsScreenclip()) {
329+
QMessageBox::warning(
330+
this,
331+
"Flameshot",
332+
tr("The registry could not be changed!") + "\n" +
333+
tr("You may start Flameshot as administrator ONCE and "
334+
"try again!"));
335+
m_registerMsScreenclip->setChecked(false);
336+
}
337+
} else {
338+
if (!unregisterMsScreenclip()) {
339+
QMessageBox::warning(
340+
this,
341+
"Flameshot",
342+
tr("The registry could not be changed!") + "\n" +
343+
tr("You may start Flameshot as administrator ONCE and "
344+
"try again!"));
345+
m_registerMsScreenclip->setChecked(true);
346+
}
347+
}
348+
});
349+
}
350+
351+
bool ShortcutsWidget::isMsScreenclipRegistered()
352+
{
353+
QSettings URLAssociations(
354+
"HKEY_LOCAL_MACHINE\\SOFTWARE\\Flameshot\\Capabilities\\URLAssociations",
355+
QSettings::NativeFormat);
356+
QString value = URLAssociations.value("ms-screenclip", "").toString();
357+
if (value.toLower() != "flameshot")
358+
return false;
359+
360+
QSettings RegisteredApplications(
361+
"HKEY_LOCAL_MACHINE\\SOFTWARE\\RegisteredApplications",
362+
QSettings::NativeFormat);
363+
value = RegisteredApplications.value("Flameshot", "").toString();
364+
if (value.toLower() !=
365+
QString("SOFTWARE\\Flameshot\\Capabilities").toLower())
366+
return false;
367+
368+
QSettings FlameshotShellCmd(
369+
"HKEY_CURRENT_USER\\Software\\Classes\\Flameshot\\Shell\\Open\\command",
370+
QSettings::NativeFormat);
371+
value = FlameshotShellCmd.value(".").toString();
372+
if (value.toLower() != QString("\"" +
373+
QDir::toNativeSeparators(
374+
QCoreApplication::applicationFilePath()) +
375+
"\" gui")
376+
.toLower())
377+
return false;
378+
379+
return true; // All registry entries found
380+
}
381+
382+
bool ShortcutsWidget::registerMsScreenclip()
383+
{
384+
QSettings URLAssociations(
385+
"HKEY_LOCAL_MACHINE\\SOFTWARE\\Flameshot\\Capabilities\\URLAssociations",
386+
QSettings::NativeFormat);
387+
URLAssociations.setValue("ms-screenclip", "Flameshot");
388+
URLAssociations.sync();
389+
if (QSettings::AccessError == URLAssociations.status()) {
390+
return false;
391+
}
392+
393+
QSettings RegisteredApplications(
394+
"HKEY_LOCAL_MACHINE\\SOFTWARE\\RegisteredApplications",
395+
QSettings::NativeFormat);
396+
RegisteredApplications.setValue("Flameshot",
397+
"SOFTWARE\\Flameshot\\Capabilities");
398+
RegisteredApplications.sync();
399+
if (QSettings::AccessError == RegisteredApplications.status()) {
400+
return false;
401+
}
402+
403+
QSettings FlameshotShellCmd(
404+
"HKEY_CURRENT_USER\\Software\\Classes\\Flameshot\\Shell\\Open\\command",
405+
QSettings::NativeFormat);
406+
FlameshotShellCmd.setValue(
407+
".",
408+
"\"" + QDir::toNativeSeparators(QCoreApplication::applicationFilePath()) +
409+
"\" gui");
410+
FlameshotShellCmd.sync();
411+
if (QSettings::AccessError == FlameshotShellCmd.status()) {
412+
return false;
413+
}
414+
415+
return isMsScreenclipRegistered();
416+
}
417+
418+
bool ShortcutsWidget::unregisterMsScreenclip()
419+
{
420+
QSettings FlameshotShellCmd("HKEY_CURRENT_USER\\Software\\Classes",
421+
QSettings::NativeFormat);
422+
FlameshotShellCmd.remove("Flameshot");
423+
FlameshotShellCmd.sync();
424+
if (QSettings::AccessError == FlameshotShellCmd.status()) {
425+
return false;
426+
}
427+
428+
QSettings RegisteredApplications(
429+
"HKEY_LOCAL_MACHINE\\SOFTWARE\\RegisteredApplications",
430+
QSettings::NativeFormat);
431+
RegisteredApplications.remove("Flameshot");
432+
RegisteredApplications.sync();
433+
if (QSettings::AccessError == RegisteredApplications.status()) {
434+
return false;
435+
}
436+
437+
QSettings URLAssociations(
438+
"HKEY_LOCAL_MACHINE\\SOFTWARE\\Flameshot\\Capabilities\\URLAssociations",
439+
QSettings::NativeFormat);
440+
URLAssociations.remove("ms-screenclip");
441+
URLAssociations.sync();
442+
if (QSettings::AccessError == URLAssociations.status()) {
443+
return false;
444+
}
445+
446+
return !isMsScreenclipRegistered();
447+
}
448+
449+
#endif

0 commit comments

Comments
 (0)