File 0001-Implement-qthotkey-plugin.patch of Package audacious-plugins

From b74c839dd2aa03a8d127d23120f2d71ce5807c4f Mon Sep 17 00:00:00 2001
From: "i.Dark_Templar" <darktemplar@dark-templar-archives.net>
Date: Mon, 13 Apr 2020 00:33:55 +0300
Subject: [PATCH 1/1] Implement qthotkey plugin

Global hotkeys plugin for qt5 interface
based on hotkey plugin for gtk interface.

Signed-off-by: Petr Vorel <petr.vorel@gmail.com>
[ upstream status: 9904ac5dfaae18ee1200cfcf64693ee3f33f739a, backported ]
---
 configure.ac             |  10 +-
 extra.mk.in              |   2 +
 meson.build              |   2 +-
 src/meson.build          |   4 +
 src/qthotkey/Makefile    |  13 +
 src/qthotkey/gui.cc      | 334 +++++++++++++++++++
 src/qthotkey/gui.h       |  67 ++++
 src/qthotkey/meson.build |  12 +
 src/qthotkey/plugin.cc   | 700 +++++++++++++++++++++++++++++++++++++++
 src/qthotkey/plugin.h    |  58 ++++
 10 files changed, 1200 insertions(+), 2 deletions(-)
 create mode 100644 src/qthotkey/Makefile
 create mode 100644 src/qthotkey/gui.cc
 create mode 100644 src/qthotkey/gui.h
 create mode 100644 src/qthotkey/meson.build
 create mode 100644 src/qthotkey/plugin.cc
 create mode 100644 src/qthotkey/plugin.h

diff --git a/configure.ac b/configure.ac
index bfc87db40..0a477db5d 100644
--- a/configure.ac
+++ b/configure.ac
@@ -103,7 +103,7 @@ check_allowed () {
                 AC_MSG_ERROR([--enable-$1 cannot be used without --enable-gtk])
             fi
             ;;
-        ampache|qtaudio|qtglspectrum|streamtuner)
+        ampache|qtaudio|qtglspectrum|streamtuner|qthotkey)
             plugin_allowed=$USE_QT
             if test $plugin_allowed = no -a $2 = yes ; then
                 AC_MSG_ERROR([--enable-$1 cannot be used without --enable-qt])
@@ -533,6 +533,13 @@ ENABLE_PLUGIN_WITH_TEST(qtglspectrum,
     auto,
     VISUALIZATION)
 
+ENABLE_PLUGIN_WITH_DEP(qthotkey,
+    global hotkeys (X11),
+    auto,
+    GENERAL,
+    QTX11EXTRAS,
+    Qt5X11Extras)
+
 dnl CoreAudio
 dnl =========
 
@@ -844,6 +851,7 @@ if test "x$USE_QT" = "xyes" ; then
     echo "  Status Icon:                            yes"
     echo "  Stream Tuner (experimental):            $have_streamtuner"
     echo "  VU Meter:                               yes"
+    echo "  X11 Global Hotkeys:                     $have_qthotkey"
     echo
 fi
 
diff --git a/extra.mk.in b/extra.mk.in
index 25baa8299..10c173034 100644
--- a/extra.mk.in
+++ b/extra.mk.in
@@ -83,6 +83,8 @@ QTOPENGL_CFLAGS ?= @QTOPENGL_CFLAGS@
 QTOPENGL_LIBS ?= @QTOPENGL_LIBS@
 QTNETWORK_CFLAGS ?= @QTNETWORK_CFLAGS@
 QTNETWORK_LIBS ?= @QTNETWORK_LIBS@
+QTX11EXTRAS_CFLAGS ?= @QTX11EXTRAS_CFLAGS@
+QTX11EXTRAS_LIBS ?= @QTX11EXTRAS_LIBS@
 VORBIS_CFLAGS ?= @VORBIS_CFLAGS@
 VORBIS_LIBS ?= @VORBIS_LIBS@
 WAVPACK_CFLAGS ?= @WAVPACK_CFLAGS@
diff --git a/meson.build b/meson.build
index acc70748b..2017dd1b0 100644
--- a/meson.build
+++ b/meson.build
@@ -42,9 +42,9 @@ xml_dep = dependency('libxml-2.0')
 if get_option('qt')
   qt_req = '>= 5.2'
   qt_dep = dependency('qt5', version: qt_req, required: true, modules: ['Core', 'Widgets', 'Gui'])
+  qtx11extras_dep = dependency('qt5', version: qt_req, required: false, modules: ['X11Extras'])
 endif
 
-
 cdaudio_enabled = false
 if get_option('cdaudio')
   libcdio_dep = dependency('libcdio', version: '>= 0.70', required: false)
diff --git a/src/meson.build b/src/meson.build
index c6ec1935a..e6f5fae82 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -90,6 +90,10 @@ if get_option('qt')
   if get_option('vumeter')
     subdir('vumeter-qt')
   endif
+
+  if qtx11extras_dep.found()
+    subdir('qthotkey')
+  endif
 endif
 
 
diff --git a/src/qthotkey/Makefile b/src/qthotkey/Makefile
new file mode 100644
index 000000000..c933e8732
--- /dev/null
+++ b/src/qthotkey/Makefile
@@ -0,0 +1,13 @@
+PLUGIN = qthotkey${PLUGIN_SUFFIX}
+
+SRCS = plugin.cc gui.cc
+
+include ../../buildsys.mk
+include ../../extra.mk
+
+plugindir := ${plugindir}/${GENERAL_PLUGIN_DIR}
+
+LD = ${CXX}
+CFLAGS += ${PLUGIN_CFLAGS}
+CPPFLAGS += ${PLUGIN_CPPFLAGS} ${QT_CFLAGS} ${QTX11EXTRAS_CFLAGS}  -I../.. -I..
+LIBS += ${QT_LIBS} ${QTX11EXTRAS_LIBS} -lX11 -laudqt
diff --git a/src/qthotkey/gui.cc b/src/qthotkey/gui.cc
new file mode 100644
index 000000000..ebdf5219f
--- /dev/null
+++ b/src/qthotkey/gui.cc
@@ -0,0 +1,334 @@
+/*
+ *  This file is part of audacious-hotkey plugin for audacious
+ *
+ *  Copyright (C) 2020 i.Dark_Templar <darktemplar@dark-templar-archives.net>
+ *  Copyright (c) 2007 - 2008  Sascha Hlusiak <contact@saschahlusiak.de>
+ *  Name: gui.c
+ *  Description: gui.c
+ *
+ *  Part of this code is from itouch-ctrl plugin.
+ *  Authors of itouch-ctrl are listed below:
+ *
+ *  Copyright (c) 2006 - 2007 Vladimir Paskov <vlado.paskov@gmail.com>
+ *
+ *  Part of this code are from xmms-itouch plugin.
+ *  Authors of xmms-itouch are listed below:
+ *
+ *  Copyright (C) 2000-2002 Ville Syrjälä <syrjala@sci.fi>
+ *                         Bryn Davies <curious@ihug.com.au>
+ *                         Jonathan A. Davis <davis@jdhouse.org>
+ *                         Jeremy Tan <nsx@nsx.homeip.net>
+ *
+ *  audacious-hotkey is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  audacious-hotkey is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with audacious-hotkey; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "plugin.h"
+#include "gui.h"
+
+#include <QtCore/QStringList>
+#include <QtCore/QMap>
+#include <QtGui/QKeyEvent>
+#include <QtGui/QMouseEvent>
+#include <QtWidgets/QApplication>
+#include <QtWidgets/QStyle>
+#include <QtX11Extras/QX11Info>
+
+#include <libaudcore/i18n.h>
+#include <libaudcore/preferences.h>
+#include <libaudcore/templates.h>
+
+#include <libaudqt/libaudqt.h>
+
+#include <X11/XKBlib.h>
+
+namespace GlobalHotkeys {
+
+static PrefWidget *last_instance = nullptr;
+
+static const QMap<Event, const char*> event_desc = {
+	{ Event::PrevTrack, N_("Previous track") },
+	{ Event::Play, N_("Play") },
+	{ Event::Pause, N_("Pause/Resume") },
+	{ Event::Stop, N_("Stop") },
+	{ Event::NextTrack, N_("Next track") },
+	{ Event::Forward, N_("Step forward") },
+	{ Event::Backward, N_("Step backward") },
+	{ Event::Mute, N_("Mute") },
+	{ Event::VolumeUp, N_("Volume up") },
+	{ Event::VolumeDown, N_("Volume down") },
+	{ Event::JumpToFile, N_("Jump to file") },
+	{ Event::ToggleWindow, N_("Toggle player window(s)") },
+	{ Event::ShowAOSD, N_("Show On-Screen-Display") },
+	{ Event::ToggleRepeat, N_("Toggle repeat") },
+	{ Event::ToggleShuffle, N_("Toggle shuffle") },
+	{ Event::ToggleStop, N_("Toggle stop after current") },
+	{ Event::Raise, N_("Raise player window(s)") },
+};
+
+class LineKeyEdit: public QLineEdit
+{
+public:
+	explicit LineKeyEdit(QWidget *parent, HotkeyConfiguration &hotkey)
+		: QLineEdit(parent),
+		m_hotkey(hotkey)
+	{
+		set_keytext(0, 0);
+	}
+
+	void set_keytext(int key, int mask)
+	{
+		QString text;
+
+		if ((key == 0) && (mask == 0))
+		{
+			text = audqt::translate_str("(none)");
+		}
+		else
+		{
+			static const char *modifier_string[] = { "Control", "Shift", "Alt", "Mod2", "Mod3", "Super", "Mod5" };
+			static const unsigned int modifiers[] = { ControlMask, ShiftMask, Mod1Mask, Mod2Mask, Mod3Mask, Mod4Mask, Mod5Mask };
+
+			QStringList strings;
+
+			KeySym keysym;
+			keysym = XkbKeycodeToKeysym(QX11Info::display(), key, 0, 0);
+			if (keysym == 0 || keysym == NoSymbol)
+			{
+				text = QString::fromLocal8Bit("#%1").arg(key);
+			}
+			else
+			{
+				text = QString::fromLocal8Bit(XKeysymToString(keysym));
+			}
+
+			for (int j = 0; j < aud::n_elems(modifiers); ++j)
+			{
+				if (mask & modifiers[j])
+				{
+					strings.push_back(QString::fromLatin1(modifier_string[j]));
+				}
+			}
+
+			if (key != 0)
+			{
+				strings.push_back(text);
+			}
+
+			text = strings.join(QLatin1String(" + "));
+		}
+
+		setText(text);
+
+		m_hotkey.key = key;
+		m_hotkey.mask = mask;
+	}
+
+protected:
+	void keyPressEvent(QKeyEvent *event) override
+	{
+		set_keytext(event->nativeScanCode(), event->nativeModifiers());
+	}
+
+private:
+	HotkeyConfiguration &m_hotkey;
+};
+
+KeyControls::~KeyControls()
+{
+	delete combobox;
+	delete keytext;
+	delete button;
+}
+
+PrefWidget::PrefWidget(QWidget *parent)
+	: QWidget(parent),
+	main_widget_layout(new QVBoxLayout(this)),
+	information_pixmap(new QLabel(this)),
+	information_label(new QLabel(audqt::translate_str("Press a key combination inside a text field."), this)),
+	information_layout(new QHBoxLayout()),
+	group_box(new QGroupBox(audqt::translate_str("Hotkeys:"), this)),
+	group_box_layout(new QGridLayout(group_box)),
+	action_label(new QLabel(audqt::translate_str("<b>Action:</b>"), group_box)),
+	key_binding_label(new QLabel(audqt::translate_str("<b>Key Binding:</b>"), group_box)),
+	add_button(new QPushButton(audqt::get_icon("list-add"), audqt::translate_str("_Add"), this)),
+	add_button_layout(new QHBoxLayout)
+{
+	int icon_size = QApplication::style()->pixelMetric(QStyle::PM_MessageBoxIconSize);
+	information_pixmap->setPixmap(QApplication::style()->standardIcon(QStyle::SP_MessageBoxInformation).pixmap(QSize(icon_size, icon_size)));
+
+	information_layout->addWidget(information_pixmap, 0, Qt::AlignLeft);
+	information_layout->addWidget(information_label, 0, Qt::AlignLeft);
+	information_layout->addStretch();
+
+	action_label->setAlignment(Qt::AlignHCenter);
+	key_binding_label->setAlignment(Qt::AlignHCenter);
+
+	group_box->setLayout(group_box_layout);
+	group_box_layout->addWidget(action_label, 0, 0);
+	group_box_layout->addWidget(key_binding_label, 0, 1);
+
+	for (const auto &hotkey: get_config()->hotkeys_list)
+	{
+		add_event_control(&hotkey);
+	}
+
+	add_button_layout->addWidget(add_button);
+	add_button_layout->addStretch();
+
+	this->setLayout(main_widget_layout);
+
+	main_widget_layout->addLayout(information_layout);
+	main_widget_layout->addWidget(group_box);
+	main_widget_layout->addLayout(add_button_layout);
+
+	QObject::connect(add_button, &QPushButton::clicked, [this]()
+	{
+		add_event_control(nullptr);
+	});
+
+	last_instance = this;
+}
+
+PrefWidget::~PrefWidget()
+{
+	delete information_layout;
+
+	for (auto control: controls_list)
+	{
+		delete control;
+	}
+
+	controls_list.clear();
+
+	if (last_instance == this)
+	{
+		last_instance = nullptr;
+	}
+}
+
+void PrefWidget::add_event_control(const HotkeyConfiguration *hotkey)
+{
+	KeyControls *control = new KeyControls;
+
+	if (hotkey != nullptr)
+	{
+		control->hotkey.key = hotkey->key;
+		control->hotkey.mask = hotkey->mask;
+		control->hotkey.event = hotkey->event;
+
+		if (control->hotkey.key == 0)
+		{
+			control->hotkey.mask = 0;
+		}
+	}
+	else
+	{
+		control->hotkey.key = 0;
+		control->hotkey.mask = 0;
+		control->hotkey.event = static_cast<Event>(0);
+	}
+
+	control->combobox = new QComboBox(group_box);
+
+	for (const auto &desc_item: event_desc)
+	{
+		control->combobox->addItem(audqt::translate_str(desc_item));
+	}
+
+	if (hotkey != nullptr)
+	{
+		control->combobox->setCurrentIndex(static_cast<int>(hotkey->event));
+	}
+
+	control->keytext = new LineKeyEdit(group_box, control->hotkey);
+	control->keytext->setFocus(Qt::OtherFocusReason);
+
+	if (hotkey != nullptr)
+	{
+		control->keytext->set_keytext(hotkey->key, hotkey->mask);
+	}
+
+	control->button = new QToolButton(group_box);
+	control->button->setIcon(audqt::get_icon("edit-delete"));
+
+	int row = group_box_layout->rowCount();
+
+	controls_list.push_back(control);
+
+	group_box_layout->addWidget(control->combobox, row, 0);
+	group_box_layout->addWidget(control->keytext, row, 1);
+	group_box_layout->addWidget(control->button, row, 2);
+
+	QObject::connect(control->button, &QToolButton::clicked, [this, control] ()
+	{
+		controls_list.removeAll(control);
+		delete control;
+	});
+}
+
+QList<HotkeyConfiguration> PrefWidget::getConfig() const
+{
+	QList<HotkeyConfiguration> result;
+
+	for (const auto &control: controls_list)
+	{
+		HotkeyConfiguration hotkey;
+
+		hotkey.key = control->hotkey.key;
+		hotkey.mask = control->hotkey.mask;
+		hotkey.event = static_cast<Event>(control->combobox->currentIndex());
+
+		result.push_back(hotkey);
+	}
+
+	return result;
+}
+
+void* PrefWidget::make_config_widget()
+{
+	ungrab_keys();
+
+	QWidget *main_widget = new PrefWidget;
+
+	return main_widget;
+}
+
+void PrefWidget::ok_callback()
+{
+	if (last_instance != nullptr)
+	{
+		PluginConfig *plugin_cfg = get_config();
+		plugin_cfg->hotkeys_list = last_instance->getConfig();
+		save_config();
+	}
+}
+
+void PrefWidget::destroy_callback()
+{
+	grab_keys();
+}
+
+static const PreferencesWidget hotkey_widgets[] = {
+	WidgetCustomQt(PrefWidget::make_config_widget)
+};
+
+const PluginPreferences hotkey_prefs = {
+	{hotkey_widgets},
+	nullptr,  // init
+	PrefWidget::ok_callback,
+	PrefWidget::destroy_callback
+};
+
+} /* namespace GlobalHotkeys */
diff --git a/src/qthotkey/gui.h b/src/qthotkey/gui.h
new file mode 100644
index 000000000..2897c21c1
--- /dev/null
+++ b/src/qthotkey/gui.h
@@ -0,0 +1,67 @@
+#ifndef _GUI_H_INCLUDED_
+#define _GUI_H_INCLUDED_
+
+#include <QtCore/QList>
+#include <QtWidgets/QComboBox>
+#include <QtWidgets/QLineEdit>
+#include <QtWidgets/QLabel>
+#include <QtWidgets/QWidget>
+#include <QtWidgets/QGroupBox>
+#include <QtWidgets/QHBoxLayout>
+#include <QtWidgets/QVBoxLayout>
+#include <QtWidgets/QGridLayout>
+#include <QtWidgets/QToolButton>
+#include <QtWidgets/QPushButton>
+
+#include "plugin.h"
+
+struct PluginPreferences;
+
+namespace GlobalHotkeys {
+
+class LineKeyEdit;
+
+extern const PluginPreferences hotkey_prefs;
+
+struct KeyControls {
+	QComboBox *combobox;
+	LineKeyEdit *keytext;
+	QToolButton *button;
+
+	HotkeyConfiguration hotkey;
+
+	~KeyControls();
+};
+
+class PrefWidget: public QWidget
+{
+public:
+	explicit PrefWidget(QWidget *parent = nullptr);
+	~PrefWidget();
+
+	QList<HotkeyConfiguration> getConfig() const;
+
+	static void* make_config_widget();
+	static void ok_callback();
+	static void destroy_callback();
+
+private:
+	QVBoxLayout *main_widget_layout;
+	QLabel *information_pixmap;
+	QLabel *information_label;
+	QHBoxLayout *information_layout;
+	QGroupBox *group_box;
+	QGridLayout *group_box_layout;
+	QLabel *action_label;
+	QLabel *key_binding_label;
+	QPushButton *add_button;
+	QHBoxLayout *add_button_layout;
+
+	QList<KeyControls* > controls_list;
+
+	void add_event_control(const HotkeyConfiguration *hotkey);
+};
+
+} /* namespace GlobalHotkeys */
+
+#endif
diff --git a/src/qthotkey/meson.build b/src/qthotkey/meson.build
new file mode 100644
index 000000000..a923ebdbf
--- /dev/null
+++ b/src/qthotkey/meson.build
@@ -0,0 +1,12 @@
+qthotkey_sources = [
+  'gui.cc',
+  'plugin.cc'
+]
+
+
+shared_module('qthotkey',
+  qthotkey_sources,
+  dependencies: [audacious_dep, qt_dep, audqt_dep, qtx11extras_dep],
+  install: true,
+  install_dir: general_plugin_dir
+)
diff --git a/src/qthotkey/plugin.cc b/src/qthotkey/plugin.cc
new file mode 100644
index 000000000..a3f68d27b
--- /dev/null
+++ b/src/qthotkey/plugin.cc
@@ -0,0 +1,700 @@
+/*
+ *  This file is part of audacious-hotkey plugin for audacious
+ *
+ *  Copyright (C) 2020 i.Dark_Templar <darktemplar@dark-templar-archives.net>
+ *  Copyright (c) 2007 - 2008  Sascha Hlusiak <contact@saschahlusiak.de>
+ *  Name: plugin.c
+ *  Description: plugin.c
+ *
+ *  Part of this code is from itouch-ctrl plugin.
+ *  Authors of itouch-ctrl are listed below:
+ *
+ *  Copyright (c) 2006 - 2007 Vladimir Paskov <vlado.paskov@gmail.com>
+ *
+ *  Part of this code are from xmms-itouch plugin.
+ *  Authors of xmms-itouch are listed below:
+ *
+ *  Copyright (C) 2000-2002 Ville Syrjälä <syrjala@sci.fi>
+ *                         Bryn Davies <curious@ihug.com.au>
+ *                         Jonathan A. Davis <davis@jdhouse.org>
+ *                         Jeremy Tan <nsx@nsx.homeip.net>
+ *
+ *  audacious-hotkey is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  audacious-hotkey is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with audacious-hotkey; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "plugin.h"
+#include "gui.h"
+
+#include <QtCore/QCoreApplication>
+#include <QtCore/QString>
+#include <QtCore/QTimer>
+#include <QtCore/QAbstractNativeEventFilter>
+#include <QtX11Extras/QX11Info>
+
+#include <libaudcore/drct.h>
+#include <libaudcore/hook.h>
+#include <libaudcore/i18n.h>
+#include <libaudcore/interface.h>
+#include <libaudcore/plugin.h>
+#include <libaudcore/runtime.h>
+
+#include <libaudqt/libaudqt.h>
+
+#include <X11/XF86keysym.h>
+#include <X11/Xlib.h>
+#include <X11/keysym.h>
+#include <xcb/xproto.h>
+
+#include <stdlib.h>
+
+namespace GlobalHotkeys {
+
+class GlobalHotkeys: public GeneralPlugin, public QAbstractNativeEventFilter
+{
+public:
+	static const char about[];
+
+	static constexpr PluginInfo info = {
+		N_("Global Hotkeys"),
+		PACKAGE,
+		about,
+		&hotkey_prefs,
+		PluginQtOnly
+	};
+
+	GlobalHotkeys();
+
+	bool init() override;
+	void cleanup() override;
+
+private:
+	bool nativeEventFilter(const QByteArray &eventType, void *message, long *result) override;
+};
+
+/* global vars */
+static PluginConfig plugin_cfg;
+
+static int grabbed = 0;
+static unsigned int numlock_mask = 0;
+static unsigned int scrolllock_mask = 0;
+static unsigned int capslock_mask = 0;
+
+const char GlobalHotkeys::about[] =
+	N_("Global Hotkey Plugin\n"
+	   "Control the player with global key combinations or multimedia keys.\n\n"
+	   "Copyright (C) 2020 i.Dark_Templar <darktemplar@dark-templar-archives.net>\n"
+	   "Copyright (C) 2007-2008 Sascha Hlusiak <contact@saschahlusiak.de>\n\n"
+	   "Contributors include:\n"
+	   "Copyright (C) 2006-2007 Vladimir Paskov <vlado.paskov@gmail.com>\n"
+	   "Copyright (C) 2000-2002 Ville Syrjälä <syrjala@sci.fi>,\n"
+	   " Bryn Davies <curious@ihug.com.au>,\n"
+	   " Jonathan A. Davis <davis@jdhouse.org>,\n"
+	   " Jeremy Tan <nsx@nsx.homeip.net>");
+
+PluginConfig* get_config()
+{
+	return &plugin_cfg;
+}
+
+/* handle keys */
+bool handle_keyevent (Event event)
+{
+	int current_volume, old_volume;
+	static int volume_static = 0;
+	bool mute;
+
+	/* get current volume */
+	current_volume = aud_drct_get_volume_main();
+	old_volume = current_volume;
+
+	if (current_volume)
+	{
+		/* volume is not mute */
+		mute = false;
+	}
+	else
+	{
+		/* volume is mute */
+		mute = true;
+	}
+
+	switch (event)
+	{
+	/* mute the playback */
+	case Event::Mute:
+		{
+			if (!mute)
+			{
+				volume_static = current_volume;
+				aud_drct_set_volume_main(0);
+				mute = true;
+			}
+			else
+			{
+				aud_drct_set_volume_main(volume_static);
+				mute = false;
+			}
+
+			return true;
+		}
+		break;
+
+	/* decrease volume */
+	case Event::VolumeDown:
+		{
+			if (mute)
+			{
+				current_volume = old_volume;
+				old_volume = 0;
+				mute = false;
+			}
+
+			if ((current_volume -= aud_get_int("volume_delta")) < 0)
+			{
+				current_volume = 0;
+			}
+
+			if (current_volume != old_volume)
+			{
+				aud_drct_set_volume_main(current_volume);
+			}
+
+			old_volume = current_volume;
+			return true;
+		}
+		break;
+
+	/* increase volume */
+	case Event::VolumeUp:
+		{
+			if (mute)
+			{
+				current_volume = old_volume;
+				old_volume = 0;
+				mute = false;
+			}
+
+			if ((current_volume += aud_get_int("volume_delta")) > 100)
+			{
+				current_volume = 100;
+			}
+
+			if (current_volume != old_volume)
+			{
+				aud_drct_set_volume_main(current_volume);
+			}
+
+			old_volume = current_volume;
+			return true;
+		}
+		break;
+
+	/* play */
+	case Event::Play:
+		{
+			aud_drct_play();
+			return true;
+		}
+		break;
+
+	/* pause */
+	case Event::Pause:
+		{
+			aud_drct_play_pause();
+			return true;
+		}
+		break;
+
+	/* stop */
+	case Event::Stop:
+		{
+			aud_drct_stop();
+			return true;
+		}
+		break;
+
+	/* prev track */
+	case Event::PrevTrack:
+		{
+			aud_drct_pl_prev();
+			return true;
+		}
+		break;
+
+	/* next track */
+	case Event::NextTrack:
+		{
+			aud_drct_pl_next();
+			return true;
+		}
+		break;
+
+	/* forward */
+	case Event::Forward:
+		{
+			aud_drct_seek(aud_drct_get_time() + aud_get_int("step_size") * 1000);
+			return true;
+		}
+		break;
+
+	/* backward */
+	case Event::Backward:
+		{
+			aud_drct_seek(aud_drct_get_time() - aud_get_int("step_size") * 1000);
+			return true;
+		}
+		break;
+
+	/* Open Jump-To-File dialog */
+	case Event::JumpToFile:
+		if (!aud_get_headless_mode())
+		{
+			aud_ui_show_jump_to_song();
+			return true;
+		}
+		break;
+
+	/* Toggle Windows */
+	case Event::ToggleWindow:
+		if (!aud_get_headless_mode())
+		{
+			aud_ui_show(!aud_ui_is_shown());
+			return true;
+		}
+		break;
+
+	/* Show OSD through AOSD plugin*/
+	case  Event::ShowAOSD:
+		{
+			hook_call("aosd toggle", nullptr);
+			return true;
+		}
+		break;
+
+	case Event::ToggleRepeat:
+		{
+			aud_toggle_bool("repeat");
+			return true;
+		}
+		break;
+
+	case Event::ToggleShuffle:
+		{
+			aud_toggle_bool("shuffle");
+			return true;
+		}
+		break;
+
+	case Event::ToggleStop:
+		{
+			aud_toggle_bool("stop_after_current_song");
+			return true;
+		}
+		break;
+
+	case Event::Raise:
+		{
+			aud_ui_show(true);
+			return true;
+		}
+		break;
+
+	/* silence warning */
+	case Event::Max:
+		break;
+	}
+
+	return false;
+}
+
+void add_hotkey(QList<HotkeyConfiguration> &hotkeys_list, KeySym keysym, int mask, Event event)
+{
+	KeyCode keycode;
+	HotkeyConfiguration hotkey;
+
+	if (keysym == 0)
+	{
+		return;
+	}
+
+	keycode = XKeysymToKeycode(QX11Info::display(), keysym);
+	if (keycode == 0)
+	{
+		return;
+	}
+
+	hotkey.key = static_cast<int>(keycode);
+	hotkey.mask = mask;
+	hotkey.event = event;
+
+	hotkeys_list.push_back(hotkey);
+}
+
+void load_defaults()
+{
+	add_hotkey(plugin_cfg.hotkeys_list, XF86XK_AudioPrev, 0, Event::PrevTrack);
+	add_hotkey(plugin_cfg.hotkeys_list, XF86XK_AudioPlay, 0, Event::Play);
+	add_hotkey(plugin_cfg.hotkeys_list, XF86XK_AudioPause, 0, Event::Pause);
+	add_hotkey(plugin_cfg.hotkeys_list, XF86XK_AudioStop, 0, Event::Stop);
+	add_hotkey(plugin_cfg.hotkeys_list, XF86XK_AudioNext, 0, Event::NextTrack);
+	add_hotkey(plugin_cfg.hotkeys_list, XF86XK_AudioMute, 0, Event::Mute);
+	add_hotkey(plugin_cfg.hotkeys_list, XF86XK_AudioRaiseVolume, 0, Event::VolumeUp);
+	add_hotkey(plugin_cfg.hotkeys_list, XF86XK_AudioLowerVolume, 0, Event::VolumeDown);
+}
+
+/* load plugin configuration */
+void load_config()
+{
+	int max = aud_get_int("globalHotkey", "NumHotkeys");
+	if (max == 0)
+	{
+		load_defaults();
+	}
+	else
+	{
+		for (int i = 0; i < max; ++i)
+		{
+			HotkeyConfiguration hotkey;
+
+			hotkey.key = aud_get_int("globalHotkey", QString::fromLatin1("Hotkey_%1_key").arg(i).toLocal8Bit().data());
+			hotkey.mask = aud_get_int("globalHotkey", QString::fromLatin1("Hotkey_%1_mask").arg(i).toLocal8Bit().data());
+			hotkey.event = static_cast<Event>(aud_get_int("globalHotkey", QString::fromLatin1("Hotkey_%1_event").arg(i).toLocal8Bit().data()));
+
+			plugin_cfg.hotkeys_list.push_back(hotkey);
+		}
+	}
+}
+
+/* save plugin configuration */
+void save_config()
+{
+	int max = 0;
+
+	for (const auto &hotkey: plugin_cfg.hotkeys_list)
+	{
+		if (hotkey.key != 0)
+		{
+			aud_set_int("globalHotkey", QString::fromLatin1("Hotkey_%1_key").arg(max).toLocal8Bit().data(), hotkey.key);
+			aud_set_int("globalHotkey", QString::fromLatin1("Hotkey_%1_mask").arg(max).toLocal8Bit().data(), hotkey.mask);
+			aud_set_int("globalHotkey", QString::fromLatin1("Hotkey_%1_event").arg(max).toLocal8Bit().data(), static_cast<int>(hotkey.event));
+			++max;
+		}
+	}
+
+	aud_set_int("globalHotkey", "NumHotkeys", max);
+}
+
+GlobalHotkeys::GlobalHotkeys()
+	: GeneralPlugin(info, false)
+{
+}
+
+bool GlobalHotkeys::init()
+{
+	audqt::init();
+
+	if (!QX11Info::isPlatformX11())
+	{
+		AUDERR("Global Hotkey plugin only supports X11.\n");
+		audqt::cleanup();
+		return false;
+	}
+
+	load_config();
+	grab_keys();
+	QCoreApplication::instance()->installNativeEventFilter(this);
+
+	return true;
+}
+
+void GlobalHotkeys::cleanup()
+{
+	QCoreApplication::instance()->removeNativeEventFilter(this);
+	ungrab_keys();
+	plugin_cfg.hotkeys_list.clear();
+
+	audqt::cleanup();
+}
+
+bool GlobalHotkeys::nativeEventFilter(const QByteArray &eventType, void *message, long *result)
+{
+	Q_UNUSED(eventType);
+	Q_UNUSED(result);
+
+	if (!grabbed)
+	{
+		return false;
+	}
+
+	xcb_generic_event_t *e = static_cast<xcb_generic_event_t*>(message);
+
+	if (e->response_type != XCB_KEY_PRESS)
+	{
+		return false;
+	}
+
+	xcb_key_press_event_t *ke = (xcb_key_press_event_t*)e;
+
+	for (const auto &hotkey: plugin_cfg.hotkeys_list)
+	{
+		if ((hotkey.key == ke->detail)
+			&& (hotkey.mask == (ke->state & ~(scrolllock_mask | numlock_mask | capslock_mask))))
+		{
+			if (handle_keyevent(hotkey.event))
+			{
+				return true;
+			}
+		}
+	}
+
+	return false;
+}
+
+/* Taken from xbindkeys */
+static void get_offending_modifiers(Display *dpy)
+{
+	XModifierKeymap *modmap;
+	KeyCode nlock, slock;
+
+	static int mask_table[8] = {
+		ShiftMask, LockMask, ControlMask, Mod1Mask,
+		Mod2Mask, Mod3Mask, Mod4Mask, Mod5Mask
+	};
+
+	nlock = XKeysymToKeycode(dpy, XK_Num_Lock);
+	slock = XKeysymToKeycode(dpy, XK_Scroll_Lock);
+
+	/*
+	* Find out the masks for the NumLock and ScrollLock modifiers,
+	* so that we can bind the grabs for when they are enabled too.
+	*/
+	modmap = XGetModifierMapping(dpy);
+
+	if ((modmap != nullptr) && (modmap->max_keypermod > 0))
+	{
+		for (int i = 0; i < 8 * modmap->max_keypermod; ++i)
+		{
+			if ((modmap->modifiermap[i] == nlock) && (nlock != 0))
+			{
+				numlock_mask = mask_table[i / modmap->max_keypermod];
+			}
+			else if ((modmap->modifiermap[i] == slock) && (slock != 0))
+			{
+				scrolllock_mask = mask_table[i / modmap->max_keypermod];
+			}
+		}
+	}
+
+	capslock_mask = LockMask;
+
+	if (modmap)
+	{
+		XFreeModifiermap (modmap);
+	}
+}
+
+
+static int x11_error_handler(Display *dpy, XErrorEvent *error)
+{
+	return 0;
+}
+
+/* grab required keys */
+static void grab_key(const HotkeyConfiguration &hotkey, Display *xdisplay, Window x_root_window)
+{
+	unsigned int modifier = hotkey.mask & ~(numlock_mask | capslock_mask | scrolllock_mask);
+
+	if (hotkey.key == 0)
+	{
+		return;
+	}
+
+	XGrabKey(xdisplay, hotkey.key, modifier, x_root_window,
+		False, GrabModeAsync, GrabModeAsync);
+
+	if (modifier == AnyModifier)
+	{
+		return;
+	}
+
+	if (numlock_mask)
+	{
+		XGrabKey(xdisplay, hotkey.key, modifier | numlock_mask,
+			x_root_window,
+			False, GrabModeAsync, GrabModeAsync);
+	}
+
+	if (capslock_mask)
+	{
+		XGrabKey(xdisplay, hotkey.key, modifier | capslock_mask,
+			x_root_window,
+			False, GrabModeAsync, GrabModeAsync);
+	}
+
+	if (scrolllock_mask)
+	{
+		XGrabKey(xdisplay, hotkey.key, modifier | scrolllock_mask,
+			x_root_window,
+			False, GrabModeAsync, GrabModeAsync);
+		}
+
+	if (numlock_mask && capslock_mask)
+	{
+		XGrabKey(xdisplay, hotkey.key, modifier | numlock_mask | capslock_mask,
+			x_root_window,
+			False, GrabModeAsync, GrabModeAsync);
+	}
+
+	if (numlock_mask && scrolllock_mask)
+	{
+		XGrabKey(xdisplay, hotkey.key, modifier | numlock_mask | scrolllock_mask,
+			x_root_window,
+			False, GrabModeAsync, GrabModeAsync);
+		}
+
+	if (capslock_mask && scrolllock_mask)
+	{
+		XGrabKey(xdisplay, hotkey.key, modifier | capslock_mask | scrolllock_mask,
+			x_root_window,
+			False, GrabModeAsync, GrabModeAsync);
+	}
+
+	if (numlock_mask && capslock_mask && scrolllock_mask)
+	{
+		XGrabKey(xdisplay, hotkey.key,
+			modifier | numlock_mask | capslock_mask | scrolllock_mask,
+			x_root_window, False, GrabModeAsync,
+			GrabModeAsync);
+	}
+}
+
+void grab_keys()
+{
+	PluginConfig *plugin_cfg = get_config();
+
+	XErrorHandler old_handler = nullptr;
+	Display *xdisplay = QX11Info::display();
+
+	if (grabbed || (!xdisplay))
+	{
+		return;
+	}
+
+	XSync(xdisplay, False);
+	old_handler = XSetErrorHandler(x11_error_handler);
+
+	get_offending_modifiers(xdisplay);
+
+	for (const auto &hotkey: plugin_cfg->hotkeys_list)
+	{
+		for (int screen = 0; screen < ScreenCount(xdisplay); ++screen)
+		{
+			grab_key(hotkey, xdisplay, RootWindow(xdisplay, screen));
+		}
+	}
+
+	XSync(xdisplay, False);
+	XSetErrorHandler(old_handler);
+
+	grabbed = 1;
+}
+
+/* grab required keys */
+static void ungrab_key(const HotkeyConfiguration &hotkey, Display *xdisplay, Window x_root_window)
+{
+	unsigned int modifier = hotkey.mask & ~(numlock_mask | capslock_mask | scrolllock_mask);
+
+	if (hotkey.key == 0)
+	{
+		return;
+	}
+
+	XUngrabKey(xdisplay, hotkey.key, modifier, x_root_window);
+
+	if (modifier == AnyModifier)
+	{
+		return;
+	}
+
+	if (numlock_mask)
+	{
+		XUngrabKey(xdisplay, hotkey.key, modifier | numlock_mask, x_root_window);
+	}
+
+	if (capslock_mask)
+	{
+		XUngrabKey(xdisplay, hotkey.key, modifier | capslock_mask, x_root_window);
+	}
+
+	if (scrolllock_mask)
+	{
+		XUngrabKey(xdisplay, hotkey.key, modifier | scrolllock_mask, x_root_window);
+	}
+
+	if (numlock_mask && capslock_mask)
+	{
+		XUngrabKey(xdisplay, hotkey.key, modifier | numlock_mask | capslock_mask, x_root_window);
+	}
+
+	if (numlock_mask && scrolllock_mask)
+	{
+		XUngrabKey(xdisplay, hotkey.key, modifier | numlock_mask | scrolllock_mask, x_root_window);
+	}
+
+	if (capslock_mask && scrolllock_mask)
+	{
+		XUngrabKey(xdisplay, hotkey.key, modifier | capslock_mask | scrolllock_mask, x_root_window);
+	}
+
+	if (numlock_mask && capslock_mask && scrolllock_mask)
+	{
+		XUngrabKey(xdisplay, hotkey.key, modifier | numlock_mask | capslock_mask | scrolllock_mask, x_root_window);
+	}
+}
+
+void ungrab_keys()
+{
+	PluginConfig *plugin_cfg = get_config();
+
+	XErrorHandler old_handler = nullptr;
+	Display *xdisplay = QX11Info::display();
+
+	if ((!grabbed) || (!xdisplay))
+	{
+		return;
+	}
+
+	XSync(xdisplay, False);
+	old_handler = XSetErrorHandler(x11_error_handler);
+
+	get_offending_modifiers(xdisplay);
+
+	for (const auto &hotkey: plugin_cfg->hotkeys_list)
+	{
+		for (int screen = 0; screen < ScreenCount(xdisplay); ++screen)
+		{
+			ungrab_key(hotkey, xdisplay, RootWindow(xdisplay, screen));
+		}
+	}
+
+	XSync(xdisplay, False);
+	XSetErrorHandler(old_handler);
+
+	grabbed = 0;
+}
+
+} /* namespace GlobalHotkeys */
+
+EXPORT GlobalHotkeys::GlobalHotkeys aud_plugin_instance;
diff --git a/src/qthotkey/plugin.h b/src/qthotkey/plugin.h
new file mode 100644
index 000000000..0a8da58a0
--- /dev/null
+++ b/src/qthotkey/plugin.h
@@ -0,0 +1,58 @@
+#ifndef _PLUGIN_H_INCLUDED_
+#define _PLUGIN_H_INCLUDED_
+
+#include <QtCore/QList>
+
+namespace GlobalHotkeys {
+
+/*
+ * Values in this enum must not be skipped and must start with 0,
+ * otherwise event_desc in gui.cc and it's use must be updated.
+ * All values except for Event::Max must be present in event_desc map.
+ */
+enum class Event {
+	PrevTrack = 0,
+	Play,
+	Pause,
+	Stop,
+	NextTrack,
+
+	Forward,
+	Backward,
+	Mute,
+	VolumeUp,
+	VolumeDown,
+	JumpToFile,
+	ToggleWindow,
+	ShowAOSD,
+
+	ToggleRepeat,
+	ToggleShuffle,
+	ToggleStop,
+
+	Raise,
+
+	Max
+};
+
+struct HotkeyConfiguration {
+	unsigned key, mask;
+	Event event;
+};
+
+struct PluginConfig {
+	/* keyboard */
+	QList<HotkeyConfiguration> hotkeys_list;
+};
+
+void load_config();
+void save_config();
+PluginConfig* get_config();
+bool handle_keyevent(Event event);
+
+void grab_keys();
+void ungrab_keys();
+
+} /* namespace GlobalHotkeys */
+
+#endif
-- 
2.26.1
openSUSE Build Service is sponsored by