diff --git a/configure.ac b/configure.ac index 7d773c7eea..6a447c2a6b 100644 --- a/configure.ac +++ b/configure.ac @@ -483,6 +483,20 @@ ENABLE_PLUGIN_WITH_DEP(hotkey, GDKX11, gdk-x11-2.0) + +test_hotkeyw32 () { + if test $HAVE_MSWINDOWS = yes ; then + have_hotkeyw32=yes + else + have_hotkeyw32=no + fi + } + +ENABLE_PLUGIN_WITH_TEST(hotkeyw32, + Windows hotkeys plugin, + auto, + GENERAL) + ENABLE_PLUGIN_WITH_DEP(aosd, X11 OSD, auto, @@ -834,6 +848,7 @@ if test "x$USE_GTK" = "xyes" ; then echo " Status Icon: yes" echo " X11 Global Hotkeys: $have_hotkey" echo " X11 On-Screen Display (aosd): $have_aosd" + echo " Windows Global hotkeys: $have_hotkeyw32" echo fi diff --git a/src/hotkey/Makefile b/src/hotkey/Makefile index 3765d51bd7..74137df663 100644 --- a/src/hotkey/Makefile +++ b/src/hotkey/Makefile @@ -1,6 +1,6 @@ PLUGIN = hotkey${PLUGIN_SUFFIX} -SRCS = plugin.cc gui.cc grab.cc +SRCS = plugin.cc gui.cc grab.cc x_hotkey.cc hotkey_api_common.cc include ../../buildsys.mk include ../../extra.mk diff --git a/src/hotkey/api_hotkey.h b/src/hotkey/api_hotkey.h new file mode 100644 index 0000000000..8caa68f4ef --- /dev/null +++ b/src/hotkey/api_hotkey.h @@ -0,0 +1,88 @@ +/* + * api_hotkey.h + * Audacious + * + * Copyright (C) 2005-2020 Audacious team + * + * XMMS - Cross-platform multimedia player + * Copyright (C) 1998-2003 Peter Alm, Mikael Alm, Olle Hallnas, + * Thomas Nilsson and 4Front Technologies + * Copyright (C) 1999-2003 Haavard Kvaalen + * + * This program 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; under version 3 of the License. + * + * This program 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 this program. If not, see . + * + * The Audacious team does not consider modular code linking to + * Audacious or using our public API to be a derived work. + */ + +#ifndef _X_HOTKEY_H_INCLUDED +#define _X_HOTKEY_H_INCLUDED + +#include +#include +#ifndef _WIN32 +#include +#include +#endif +#include "plugin.h" + +class Hotkey +{ + + //#ifdef _WIN32 + //#define OS_KeySym int + //#else + //#define OS_KeySym KeySym + //#endif + +#ifdef _WIN32 + typedef int OS_KeySym; +#else + typedef KeySym OS_KeySym; +#endif + +public: + static void set_keytext(GtkWidget * entry, int key, int mask, int type); + static std::pair get_is_mod(GdkEventKey * event); + template + static int calculate_mod(T_GDK_EVENT * event); + static void add_hotkey(HotkeyConfiguration ** pphotkey, OS_KeySym keysym, + int mask, int type, EVENT event); + static void key_to_string(int key, char ** out_keytext); + static char * create_human_readable_keytext(const char * const keytext, + int key, int mask); +}; + +#ifndef _WIN32 +#define OS_KEY_AudioPrev XF86XK_AudioPrev +#define OS_KEY_AudioPlay XF86XK_AudioPlay +#define OS_KEY_AudioPause XF86XK_AudioPause +#define OS_KEY_AudioStop XF86XK_AudioStop +#define OS_KEY_AudioNext XF86XK_AudioNext +#define OS_KEY_AudioMute XF86XK_AudioMute +#define OS_KEY_AudioRaiseVolume XF86XK_AudioRaiseVolume +#define OS_KEY_AudioLowerVolume XF86XK_AudioLowerVolume +#else + +#define OS_KEY_AudioPrev 0 +#define OS_KEY_AudioPlay 0 +#define OS_KEY_AudioPause 0 +#define OS_KEY_AudioStop 0 +#define OS_KEY_AudioNext 0 +#define OS_KEY_AudioMute 0 +#define OS_KEY_AudioRaiseVolume 0 +#define OS_KEY_AudioLowerVolume 0 + +#endif + +#endif //_X_HOTKEY_H_INCLUDED diff --git a/src/hotkey/grab.cc b/src/hotkey/grab.cc index febb96b34a..1b6c7ba8ef 100644 --- a/src/hotkey/grab.cc +++ b/src/hotkey/grab.cc @@ -2,8 +2,8 @@ * This file is part of audacious-hotkey plugin for audacious * * Copyright (c) 2007 - 2008 Sascha Hlusiak - * Name: grab.c - * Description: grab.c + * Name: grab.cc + * Description: grab.cc * * Part of this code is from itouch-ctrl plugin. * Authors of itouch-ctrl are listed below: diff --git a/src/hotkey/grab.h b/src/hotkey/grab.h index 47935dbbd3..8860017902 100644 --- a/src/hotkey/grab.h +++ b/src/hotkey/grab.h @@ -1,3 +1,30 @@ +/* + * grab.h + * Audacious + * + * Copyright (C) 2005-2020 Audacious team + * + * XMMS - Cross-platform multimedia player + * Copyright (C) 1998-2003 Peter Alm, Mikael Alm, Olle Hallnas, + * Thomas Nilsson and 4Front Technologies + * Copyright (C) 1999-2003 Haavard Kvaalen + * + * This program 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; under version 3 of the License. + * + * This program 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 this program. If not, see . + * + * The Audacious team does not consider modular code linking to + * Audacious or using our public API to be a derived work. + */ + #ifndef _GRAB_H_INCLUDED_ #define _GRAB_H_INCLUDED_ diff --git a/src/hotkey/gui.cc b/src/hotkey/gui.cc index a4d940f0ea..342c5bc59e 100644 --- a/src/hotkey/gui.cc +++ b/src/hotkey/gui.cc @@ -2,8 +2,8 @@ * This file is part of audacious-hotkey plugin for audacious * * Copyright (c) 2007 - 2008 Sascha Hlusiak - * Name: gui.c - * Description: gui.c + * Name: gui.cc + * Description: gui.cc * * Part of this code is from itouch-ctrl plugin. * Authors of itouch-ctrl are listed below: @@ -40,11 +40,12 @@ #include #include -#include #include -#include +#include +#include +#include "api_hotkey.h" #include "grab.h" #include "gui.h" #include "plugin.h" @@ -86,68 +87,10 @@ static const char * event_desc[EVENT_MAX] = { [EVENT_TOGGLE_STOP] = N_("Toggle stop after current"), [EVENT_RAISE] = N_("Raise player window(s)")}; -static void set_keytext(GtkWidget * entry, int key, int mask, int type) -{ - char * text = nullptr; - - if (key == 0 && mask == 0) - { - text = g_strdup(_("(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}; - const char * strings[9]; - char * keytext = nullptr; - int i, j; - if (type == TYPE_KEY) - { - KeySym keysym; - keysym = XkbKeycodeToKeysym( - GDK_DISPLAY_XDISPLAY(gdk_display_get_default()), key, 0, 0); - if (keysym == 0 || keysym == NoSymbol) - { - keytext = g_strdup_printf("#%d", key); - } - else - { - keytext = g_strdup(XKeysymToString(keysym)); - } - } - if (type == TYPE_MOUSE) - { - keytext = g_strdup_printf("Button%d", key); - } - - for (i = 0, j = 0; j < 7; j++) - { - if (mask & modifiers[j]) - strings[i++] = modifier_string[j]; - } - if (key != 0) - strings[i++] = keytext; - strings[i] = nullptr; - - text = g_strjoinv(" + ", (char **)strings); - g_free(keytext); - } - - gtk_entry_set_text(GTK_ENTRY(entry), text); - gtk_editable_set_position(GTK_EDITABLE(entry), -1); - if (text) - g_free(text); -} - static gboolean on_entry_key_press_event(GtkWidget * widget, GdkEventKey * event, void * user_data) { KeyControls * controls = (KeyControls *)user_data; - int is_mod; - int mod; if (event->keyval == GDK_Tab) return false; @@ -155,10 +98,14 @@ static gboolean on_entry_key_press_event(GtkWidget * widget, return false; if (event->keyval == GDK_Return && ((event->state & ~GDK_LOCK_MASK) == 0)) return false; +#ifdef _WIN32 + if (event->keyval == GDK_Meta_L || event->keyval == GDK_Meta_R) + return false; +#endif if (event->keyval == GDK_ISO_Left_Tab) { - set_keytext(controls->keytext, controls->hotkey.key, - controls->hotkey.mask, controls->hotkey.type); + Hotkey::set_keytext(controls->keytext, controls->hotkey.key, + controls->hotkey.mask, controls->hotkey.type); return false; } if (event->keyval == GDK_Up && ((event->state & ~GDK_LOCK_MASK) == 0)) @@ -166,32 +113,8 @@ static gboolean on_entry_key_press_event(GtkWidget * widget, if (event->keyval == GDK_Down && ((event->state & ~GDK_LOCK_MASK) == 0)) return false; - mod = 0; - is_mod = 0; - - if ((event->state & GDK_CONTROL_MASK) | - (!is_mod && (is_mod = (event->keyval == GDK_Control_L || - event->keyval == GDK_Control_R)))) - mod |= ControlMask; - - if ((event->state & GDK_MOD1_MASK) | - (!is_mod && - (is_mod = (event->keyval == GDK_Alt_L || event->keyval == GDK_Alt_R)))) - mod |= Mod1Mask; - - if ((event->state & GDK_SHIFT_MASK) | - (!is_mod && (is_mod = (event->keyval == GDK_Shift_L || - event->keyval == GDK_Shift_R)))) - mod |= ShiftMask; - - if ((event->state & GDK_MOD5_MASK) | - (!is_mod && (is_mod = (event->keyval == GDK_ISO_Level3_Shift)))) - mod |= Mod5Mask; - - if ((event->state & GDK_MOD4_MASK) | - (!is_mod && (is_mod = (event->keyval == GDK_Super_L || - event->keyval == GDK_Super_R)))) - mod |= Mod4Mask; + int mod, is_mod; + std::tie(mod, is_mod) = Hotkey::get_is_mod(event); if (!is_mod) { @@ -204,8 +127,10 @@ static gboolean on_entry_key_press_event(GtkWidget * widget, gtk_widget_grab_focus(GTK_WIDGET(controls->next->keytext)); } - set_keytext(controls->keytext, is_mod ? 0 : event->hardware_keycode, mod, - TYPE_KEY); + Hotkey::set_keytext(controls->keytext, is_mod ? 0 : event->hardware_keycode, + mod, TYPE_KEY); + // Returning TRUE indicates that the event has been handled, and that it + // should not propagate further. return true; } @@ -216,8 +141,8 @@ static gboolean on_entry_key_release_event(GtkWidget * widget, KeyControls * controls = (KeyControls *)user_data; if (!gtk_widget_is_focus(widget)) return false; - set_keytext(controls->keytext, controls->hotkey.key, controls->hotkey.mask, - controls->hotkey.type); + Hotkey::set_keytext(controls->keytext, controls->hotkey.key, + controls->hotkey.mask, controls->hotkey.type); return true; } @@ -227,26 +152,11 @@ static gboolean on_entry_button_press_event(GtkWidget * widget, void * user_data) { KeyControls * controls = (KeyControls *)user_data; - int mod; if (!gtk_widget_is_focus(widget)) return false; - mod = 0; - if (event->state & GDK_CONTROL_MASK) - mod |= ControlMask; - - if (event->state & GDK_MOD1_MASK) - mod |= Mod1Mask; - - if (event->state & GDK_SHIFT_MASK) - mod |= ShiftMask; - - if (event->state & GDK_MOD5_MASK) - mod |= Mod5Mask; - - if (event->state & GDK_MOD4_MASK) - mod |= Mod4Mask; + int mod = Hotkey::calculate_mod(event); if ((event->button <= 3) && (mod == 0)) { @@ -268,8 +178,8 @@ static gboolean on_entry_button_press_event(GtkWidget * widget, controls->hotkey.key = event->button; controls->hotkey.mask = mod; controls->hotkey.type = TYPE_MOUSE; - set_keytext(controls->keytext, controls->hotkey.key, controls->hotkey.mask, - controls->hotkey.type); + Hotkey::set_keytext(controls->keytext, controls->hotkey.key, + controls->hotkey.mask, controls->hotkey.type); if (controls->next == nullptr) add_callback(nullptr, (void *)controls); @@ -280,26 +190,11 @@ static gboolean on_entry_scroll_event(GtkWidget * widget, GdkEventScroll * event, void * user_data) { KeyControls * controls = (KeyControls *)user_data; - int mod; if (!gtk_widget_is_focus(widget)) return false; - mod = 0; - if (event->state & GDK_CONTROL_MASK) - mod |= ControlMask; - - if (event->state & GDK_MOD1_MASK) - mod |= Mod1Mask; - - if (event->state & GDK_SHIFT_MASK) - mod |= ShiftMask; - - if (event->state & GDK_MOD5_MASK) - mod |= Mod5Mask; - - if (event->state & GDK_MOD4_MASK) - mod |= Mod4Mask; + auto mod = Hotkey::calculate_mod(event); if (event->direction == GDK_SCROLL_UP) controls->hotkey.key = 4; @@ -314,8 +209,8 @@ static gboolean on_entry_scroll_event(GtkWidget * widget, controls->hotkey.mask = mod; controls->hotkey.type = TYPE_MOUSE; - set_keytext(controls->keytext, controls->hotkey.key, controls->hotkey.mask, - controls->hotkey.type); + Hotkey::set_keytext(controls->keytext, controls->hotkey.key, + controls->hotkey.mask, controls->hotkey.type); if (controls->next == nullptr) add_callback(nullptr, (void *)controls); return true; @@ -367,8 +262,8 @@ KeyControls * add_event_controls(KeyControls * list, GtkWidget * grid, int row, row + 1); gtk_editable_set_editable(GTK_EDITABLE(controls->keytext), false); - set_keytext(controls->keytext, controls->hotkey.key, controls->hotkey.mask, - controls->hotkey.type); + Hotkey::set_keytext(controls->keytext, controls->hotkey.key, + controls->hotkey.mask, controls->hotkey.type); g_signal_connect((void *)controls->keytext, "key_press_event", G_CALLBACK(on_entry_key_press_event), controls); g_signal_connect((void *)controls->keytext, "key_release_event", @@ -408,7 +303,6 @@ void * make_config_widget() load_config(); plugin_cfg = get_config(); - ungrab_keys(); main_vbox = gtk_vbox_new(false, 4); @@ -513,7 +407,7 @@ static void clear_keyboard(GtkWidget * widget, void * data) controls->hotkey.key = 0; controls->hotkey.mask = 0; controls->hotkey.type = TYPE_KEY; - set_keytext(controls->keytext, 0, 0, TYPE_KEY); + Hotkey::set_keytext(controls->keytext, 0, 0, TYPE_KEY); gtk_combo_box_set_active(GTK_COMBO_BOX(controls->combobox), 0); return; } @@ -609,9 +503,7 @@ void add_callback(GtkWidget * widget, void * data) void destroy_callback() { KeyControls * controls = first_controls; - grab_keys(); - while (controls) { KeyControls * old; diff --git a/src/hotkey/gui.h b/src/hotkey/gui.h index 78e6562d8b..3239d4a24f 100644 --- a/src/hotkey/gui.h +++ b/src/hotkey/gui.h @@ -1,3 +1,30 @@ +/* + * gui.h + * Audacious + * + * Copyright (C) 2005-2020 Audacious team + * + * XMMS - Cross-platform multimedia player + * Copyright (C) 1998-2003 Peter Alm, Mikael Alm, Olle Hallnas, + * Thomas Nilsson and 4Front Technologies + * Copyright (C) 1999-2003 Haavard Kvaalen + * + * This program 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; under version 3 of the License. + * + * This program 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 this program. If not, see . + * + * The Audacious team does not consider modular code linking to + * Audacious or using our public API to be a derived work. + */ + #ifndef _GUI_H_INCLUDED_ #define _GUI_H_INCLUDED_ diff --git a/src/hotkey/hotkey_api_common.cc b/src/hotkey/hotkey_api_common.cc new file mode 100644 index 0000000000..f077dd14bb --- /dev/null +++ b/src/hotkey/hotkey_api_common.cc @@ -0,0 +1,124 @@ +/* + * hotkey_api_common.cc + * Audacious + * + * Copyright (C) 2005-2020 Audacious team + * + * XMMS - Cross-platform multimedia player + * Copyright (C) 1998-2003 Peter Alm, Mikael Alm, Olle Hallnas, + * Thomas Nilsson and 4Front Technologies + * Copyright (C) 1999-2003 Haavard Kvaalen + * + * This program 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; under version 3 of the License. + * + * This program 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 this program. If not, see . + * + * The Audacious team does not consider modular code linking to + * Audacious or using our public API to be a derived work. + */ + +#include "api_hotkey.h" +#include +#include +#include +#include +#include +#ifdef _WIN32 +#include +#include +#endif + +template +int Hotkey::calculate_mod(T_GDK_EVENT * event) +{ + int mod = 0; + if (event->state & GDK_CONTROL_MASK) + mod |= HK_CONTROL_MASK; + + if (event->state & GDK_MOD1_MASK) + mod |= HK_MOD1_ALT_MASK; + + if (event->state & GDK_SHIFT_MASK) + mod |= HK_SHIFT_MASK; + + if (event->state & GDK_MOD5_MASK) + mod |= HK_MOD5_MASK; + + if (event->state & GDK_MOD4_MASK) + mod |= HK_MOD4_MASK; + return mod; +} + +template int Hotkey::calculate_mod(GdkEventScroll * event); +template int Hotkey::calculate_mod(GdkEventButton * event); +// template int Hotkey::calculate_mod(GdkEventKey* event); + +std::pair Hotkey::get_is_mod(GdkEventKey * event) +{ + int mod = 0; + int is_mod = 0; + + if ((event->state & GDK_CONTROL_MASK) | + (!is_mod && (is_mod = (event->keyval == GDK_Control_L || + event->keyval == GDK_Control_R)))) + mod |= HK_CONTROL_MASK; + + if ((event->state & GDK_MOD1_MASK) | + (!is_mod && + (is_mod = (event->keyval == GDK_Alt_L || event->keyval == GDK_Alt_R)))) + mod |= HK_MOD1_ALT_MASK; + + if ((event->state & GDK_SHIFT_MASK) | + (!is_mod && (is_mod = (event->keyval == GDK_Shift_L || + event->keyval == GDK_Shift_R)))) + mod |= HK_SHIFT_MASK; + + if ((event->state & GDK_MOD5_MASK) | + (!is_mod && (is_mod = (event->keyval == GDK_ISO_Level3_Shift)))) + mod |= HK_MOD5_MASK; + + if ((event->state & GDK_MOD4_MASK) + + | (!is_mod && (is_mod = (event->keyval == GDK_Super_L || + event->keyval == GDK_Super_R)))) + mod |= HK_MOD4_MASK; + + return std::make_pair(mod, is_mod); +} + +void Hotkey::set_keytext(GtkWidget * entry, int key, int mask, int type) +{ + char * text = nullptr; + + if (key == 0 && mask == 0) + { + text = g_strdup(_("(none)")); + } + else + { + char * keytext = nullptr; + if (type == TYPE_KEY) + { + Hotkey::key_to_string(key, &keytext); + } + if (type == TYPE_MOUSE) + { + keytext = g_strdup_printf("Button%d", key); + } + text = Hotkey::create_human_readable_keytext(keytext, key, mask); + g_free(keytext); + } + + gtk_entry_set_text(GTK_ENTRY(entry), text); + gtk_editable_set_position(GTK_EDITABLE(entry), -1); + if (text) + g_free(text); +} diff --git a/src/hotkey/plugin.cc b/src/hotkey/plugin.cc index e1a81ebc16..4ac42faa44 100644 --- a/src/hotkey/plugin.cc +++ b/src/hotkey/plugin.cc @@ -2,8 +2,8 @@ * This file is part of audacious-hotkey plugin for audacious * * Copyright (c) 2007 - 2008 Sascha Hlusiak - * Name: plugin.c - * Description: plugin.c + * Name: plugin.cc + * Description: plugin.cc * * Part of this code is from itouch-ctrl plugin. * Authors of itouch-ctrl are listed below: @@ -34,12 +34,6 @@ * USA. */ -#include - -#include - -#include -#include #include #include @@ -49,9 +43,11 @@ #include #include +#include "api_hotkey.h" #include "grab.h" #include "gui.h" -#include "plugin.h" + +extern bool system_up_and_running; class GlobalHotkeys : public GeneralPlugin { @@ -63,27 +59,28 @@ class GlobalHotkeys : public GeneralPlugin constexpr GlobalHotkeys() : GeneralPlugin(info, false) {} - bool init(); - void cleanup(); + bool init() override; + void cleanup() override; }; -EXPORT GlobalHotkeys aud_plugin_instance; - /* global vars */ -static PluginConfig plugin_cfg; +EXPORT GlobalHotkeys aud_plugin_instance; +PluginConfig plugin_cfg_gtk_global_hk; const char GlobalHotkeys::about[] = N_("Global Hotkey Plugin\n" - "Control the player with global key combinations or multimedia keys.\n\n" + "Control the player with global key combinations or multimedia " + "keys.\n\n" "Copyright (C) 2007-2008 Sascha Hlusiak \n\n" "Contributors include:\n" + "Copyright (C) 2020 Domen Mori \n" "Copyright (C) 2006-2007 Vladimir Paskov \n" "Copyright (C) 2000-2002 Ville Syrjälä ,\n" " Bryn Davies ,\n" " Jonathan A. Davis ,\n" " Jeremy Tan "); -PluginConfig * get_config() { return &plugin_cfg; } +PluginConfig * get_config() { return &plugin_cfg_gtk_global_hk; } /* * plugin activated @@ -95,11 +92,13 @@ bool GlobalHotkeys::init() AUDERR("GTK+ initialization failed.\n"); return false; } - +#ifdef _WIN32 + win_init(); +#else setup_filter(); +#endif load_config(); grab_keys(); - return true; } @@ -286,57 +285,24 @@ gboolean handle_keyevent(EVENT event) return false; } -void add_hotkey(HotkeyConfiguration ** pphotkey, KeySym keysym, int mask, - int type, EVENT event) -{ - KeyCode keycode; - HotkeyConfiguration * photkey; - if (keysym == 0) - return; - if (pphotkey == nullptr) - return; - photkey = *pphotkey; - if (photkey == nullptr) - return; - keycode = XKeysymToKeycode(GDK_DISPLAY_XDISPLAY(gdk_display_get_default()), - keysym); - if (keycode == 0) - return; - if (photkey->key) - { - photkey->next = g_new(HotkeyConfiguration, 1); - photkey = photkey->next; - *pphotkey = photkey; - photkey->next = nullptr; - } - photkey->key = (int)keycode; - photkey->mask = mask; - photkey->event = event; - photkey->type = type; -} - void load_defaults() { HotkeyConfiguration * hotkey; - hotkey = &(plugin_cfg.first); - - add_hotkey(&hotkey, XF86XK_AudioPrev, 0, TYPE_KEY, EVENT_PREV_TRACK); - add_hotkey(&hotkey, XF86XK_AudioPlay, 0, TYPE_KEY, EVENT_PLAY); - add_hotkey(&hotkey, XF86XK_AudioPause, 0, TYPE_KEY, EVENT_PAUSE); - add_hotkey(&hotkey, XF86XK_AudioStop, 0, TYPE_KEY, EVENT_STOP); - add_hotkey(&hotkey, XF86XK_AudioNext, 0, TYPE_KEY, EVENT_NEXT_TRACK); - - /* add_hotkey(&hotkey, XF86XK_AudioRewind, 0, TYPE_KEY, EVENT_BACKWARD); - */ - - add_hotkey(&hotkey, XF86XK_AudioMute, 0, TYPE_KEY, EVENT_MUTE); - add_hotkey(&hotkey, XF86XK_AudioRaiseVolume, 0, TYPE_KEY, EVENT_VOL_UP); - add_hotkey(&hotkey, XF86XK_AudioLowerVolume, 0, TYPE_KEY, EVENT_VOL_DOWN); - - /* add_hotkey(&hotkey, XF86XK_AudioMedia, 0, TYPE_KEY, - EVENT_JUMP_TO_FILE); add_hotkey(&hotkey, XF86XK_Music, 0, TYPE_KEY, - EVENT_TOGGLE_WIN); */ + hotkey = &(plugin_cfg_gtk_global_hk.first); + + Hotkey::add_hotkey(&hotkey, OS_KEY_AudioPrev, 0, TYPE_KEY, + EVENT_PREV_TRACK); + Hotkey::add_hotkey(&hotkey, OS_KEY_AudioPlay, 0, TYPE_KEY, EVENT_PLAY); + Hotkey::add_hotkey(&hotkey, OS_KEY_AudioPause, 0, TYPE_KEY, EVENT_PAUSE); + Hotkey::add_hotkey(&hotkey, OS_KEY_AudioStop, 0, TYPE_KEY, EVENT_STOP); + Hotkey::add_hotkey(&hotkey, OS_KEY_AudioNext, 0, TYPE_KEY, + EVENT_NEXT_TRACK); + Hotkey::add_hotkey(&hotkey, OS_KEY_AudioMute, 0, TYPE_KEY, EVENT_MUTE); + Hotkey::add_hotkey(&hotkey, OS_KEY_AudioRaiseVolume, 0, TYPE_KEY, + EVENT_VOL_UP); + Hotkey::add_hotkey(&hotkey, OS_KEY_AudioLowerVolume, 0, TYPE_KEY, + EVENT_VOL_DOWN); } /* load plugin configuration */ @@ -345,7 +311,7 @@ void load_config() HotkeyConfiguration * hotkey; int i, max; - hotkey = &(plugin_cfg.first); + hotkey = &(plugin_cfg_gtk_global_hk.first); hotkey->next = nullptr; hotkey->key = 0; hotkey->mask = 0; @@ -394,7 +360,7 @@ void save_config() int max; HotkeyConfiguration * hotkey; - hotkey = &(plugin_cfg.first); + hotkey = &(plugin_cfg_gtk_global_hk.first); max = 0; while (hotkey) { @@ -427,10 +393,13 @@ void save_config() void GlobalHotkeys::cleanup() { +#ifdef _WIN32 + system_up_and_running = false; +#endif HotkeyConfiguration * hotkey; ungrab_keys(); release_filter(); - hotkey = &(plugin_cfg.first); + hotkey = &(plugin_cfg_gtk_global_hk.first); hotkey = hotkey->next; while (hotkey) { @@ -439,8 +408,8 @@ void GlobalHotkeys::cleanup() hotkey = hotkey->next; g_free(old); } - plugin_cfg.first.next = nullptr; - plugin_cfg.first.key = 0; - plugin_cfg.first.event = (EVENT)0; - plugin_cfg.first.mask = 0; + plugin_cfg_gtk_global_hk.first.next = nullptr; + plugin_cfg_gtk_global_hk.first.key = 0; + plugin_cfg_gtk_global_hk.first.event = (EVENT)0; + plugin_cfg_gtk_global_hk.first.mask = 0; } diff --git a/src/hotkey/plugin.h b/src/hotkey/plugin.h index d7dbbfd608..d07b2ee317 100644 --- a/src/hotkey/plugin.h +++ b/src/hotkey/plugin.h @@ -1,3 +1,39 @@ +/* + * This file is part of audacious-hotkey plugin for audacious + * + * Copyright (c) 2007 - 2008 Sascha Hlusiak + * Name: plugin.h + * Description: plugin.h + * + * Part of this code is from itouch-ctrl plugin. + * Authors of itouch-ctrl are listed below: + * + * Copyright (c) 2006 - 2007 Vladimir Paskov + * + * Part of this code are from xmms-itouch plugin. + * Authors of xmms-itouch are listed below: + * + * Copyright (C) 2000-2002 Ville Syrjälä + * Bryn Davies + * Jonathan A. Davis + * Jeremy Tan + * + * 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. + */ + #ifndef _PLUGIN_H_INCLUDED_ #define _PLUGIN_H_INCLUDED_ @@ -6,6 +42,25 @@ #define TYPE_KEY 0 #define TYPE_MOUSE 1 +#ifdef _WIN32 +#define HK_CONTROL_MASK MOD_CONTROL +#define HK_SHIFT_MASK MOD_SHIFT +#define HK_MOD1_ALT_MASK MOD_ALT +#define HK_MOD2_MASK MOD_WIN +#define HK_MOD3_MASK 0x0016 +#define HK_MOD4_MASK 0x0032 +#define HK_MOD5_MASK 0x0064 +#define HK_WIN_NONREPEAT MOD_NOREPEAT +#else +#define HK_CONTROL_MASK ControlMask +#define HK_SHIFT_MASK ShiftMask +#define HK_MOD1_ALT_MASK Mod1Mask +#define HK_MOD2_MASK Mod2Mask +#define HK_MOD3_MASK Mod3Mask +#define HK_MOD4_MASK Mod4Mask +#define HK_MOD5_MASK Mod5Mask +#endif + typedef enum { EVENT_PREV_TRACK = 0, @@ -50,5 +105,9 @@ void load_config(); void save_config(); PluginConfig * get_config(); gboolean handle_keyevent(EVENT event); +#ifdef _WIN32 +void win_init(); +#endif +extern PluginConfig plugin_cfg_gtk_global_hk; #endif diff --git a/src/hotkey/x_hotkey.cc b/src/hotkey/x_hotkey.cc new file mode 100644 index 0000000000..1b14904395 --- /dev/null +++ b/src/hotkey/x_hotkey.cc @@ -0,0 +1,99 @@ +/* + * x_hotkey.cc + * Audacious + * + * Copyright (C) 2005-2020 Audacious team + * + * XMMS - Cross-platform multimedia player + * Copyright (C) 1998-2003 Peter Alm, Mikael Alm, Olle Hallnas, + * Thomas Nilsson and 4Front Technologies + * Copyright (C) 1999-2003 Haavard Kvaalen + * + * This program 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; under version 3 of the License. + * + * This program 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 this program. If not, see . + * + * The Audacious team does not consider modular code linking to + * Audacious or using our public API to be a derived work. + */ + +#include +#include +#include +#include + +#include "api_hotkey.h" + +void Hotkey::add_hotkey(HotkeyConfiguration ** pphotkey, OS_KeySym keysym, + int mask, int type, EVENT event) +{ + KeyCode keycode; + HotkeyConfiguration * photkey; + if (keysym == 0) + return; + if (pphotkey == nullptr) + return; + photkey = *pphotkey; + if (photkey == nullptr) + return; + keycode = XKeysymToKeycode(GDK_DISPLAY_XDISPLAY(gdk_display_get_default()), + keysym); + if (keycode == 0) + return; + if (photkey->key) + { + photkey->next = g_new(HotkeyConfiguration, 1); + photkey = photkey->next; + *pphotkey = photkey; + photkey->next = nullptr; + } + photkey->key = (int)keycode; + photkey->mask = mask; + photkey->event = event; + photkey->type = type; +} + +void Hotkey::key_to_string(int key, char ** out_keytext) +{ + KeySym keysym; + keysym = XkbKeycodeToKeysym(GDK_DISPLAY_XDISPLAY(gdk_display_get_default()), + key, 0, 0); + if (keysym == 0 || keysym == NoSymbol) + { + *out_keytext = g_strdup_printf("#%d", key); + } + else + { + *out_keytext = g_strdup(XKeysymToString(keysym)); + } +} + +char * Hotkey::create_human_readable_keytext(const char * const keytext, + int key, int mask) +{ + static const constexpr unsigned int modifiers[] = { + HK_CONTROL_MASK, HK_SHIFT_MASK, HK_MOD1_ALT_MASK, HK_MOD2_MASK, + HK_MOD3_MASK, HK_MOD4_MASK, HK_MOD5_MASK}; + static const constexpr char * const modifier_string[] = { + "Control", "Shift", "Alt", "Mod2", "Mod3", "Super", "Mod5"}; + const char * strings[9]; + int i, j; + for (i = 0, j = 0; j < 7; j++) + { + if (mask & modifiers[j]) + strings[i++] = modifier_string[j]; + } + if (key != 0) + strings[i++] = keytext; + strings[i] = nullptr; + + return g_strjoinv(" + ", (char **)strings); +} \ No newline at end of file diff --git a/src/hotkeyw32/Makefile b/src/hotkeyw32/Makefile new file mode 100644 index 0000000000..95bdbcc2bb --- /dev/null +++ b/src/hotkeyw32/Makefile @@ -0,0 +1,13 @@ +PLUGIN = hotkeyw32${PLUGIN_SUFFIX} + +SRCS = ../hotkey/gui.cc ../hotkey/plugin.cc w32_hotkey.cc ../hotkey/hotkey_api_common.cc windows_window.cc + +include ../../buildsys.mk +include ../../extra.mk + +plugindir := ${plugindir}/${GENERAL_PLUGIN_DIR} + +LD = ${CXX} +CFLAGS += ${PLUGIN_CFLAGS} +CPPFLAGS += ${PLUGIN_CPPFLAGS} ${GTK_CFLAGS} ${GLIB_CFLAGS} -I../.. -I.. +LIBS += ${GLIB_LIBS} ${GTK_LIBS} -laudgui \ No newline at end of file diff --git a/src/hotkeyw32/w32_hotkey.cc b/src/hotkeyw32/w32_hotkey.cc new file mode 100644 index 0000000000..666e9f6a84 --- /dev/null +++ b/src/hotkeyw32/w32_hotkey.cc @@ -0,0 +1,362 @@ +/* + * w32_hotkey.cc + * Audacious + * + * Copyright (C) 2005-2020 Audacious team + * + * XMMS - Cross-platform multimedia player + * Copyright (C) 1998-2003 Peter Alm, Mikael Alm, Olle Hallnas, + * Thomas Nilsson and 4Front Technologies + * Copyright (C) 1999-2003 Haavard Kvaalen + * + * This program 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; under version 3 of the License. + * + * This program 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 this program. If not, see . + * + * The Audacious team does not consider modular code linking to + * Audacious or using our public API to be a derived work. + */ + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "windows_key_str_map.h" +#include "windows_window.h" + +constexpr auto W_KEY_ID_PLAY = 18771; +constexpr auto W_KEY_ID_PREV = 18772; +constexpr auto W_KEY_ID_NEXT = 18773; +constexpr auto W_FIRST_GLOBAL_KEY_ID = 18774; + +bool system_up_and_running{false}; + +/** + * Handle to window that has thumbbar buttons associated. Nullptr if it is not + * on display. + */ +WindowsWindow message_receiving_window{ + nullptr, {}, {}, AudaciousWindowKind::UNKNOWN}; + +template +std::string VirtualKeyCodeToStringMethod2(T virtualKey) +{ + wchar_t name[128]; + static auto key_layout = GetKeyboardLayout(0); + UINT scanCode = MapVirtualKeyEx(virtualKey, MAPVK_VK_TO_VSC, key_layout); + LONG lParamValue = (scanCode << 16); + int result = GetKeyNameTextW(lParamValue, name, sizeof name); + if (result > 0) + { + auto * windows_cmd = reinterpret_cast( + g_utf16_to_utf8(reinterpret_cast(name), -1, + nullptr, nullptr, nullptr)); + std::string returning(windows_cmd); + g_free(windows_cmd); + return returning; + } + else + { + return {}; + } +} + +void grab_keys_onto_window(); + +void Hotkey::add_hotkey(HotkeyConfiguration ** pphotkey, + Hotkey::OS_KeySym keysym, int mask, int type, + EVENT event) +{ + HotkeyConfiguration * photkey; + if (keysym == 0) + return; + if (pphotkey == nullptr) + return; + photkey = *pphotkey; + if (photkey == nullptr) + return; + if (photkey->key) + { + photkey->next = g_new(HotkeyConfiguration, 1); + photkey = photkey->next; + *pphotkey = photkey; + photkey->next = nullptr; + } + photkey->key = (int)42; + photkey->mask = mask; + photkey->event = event; + photkey->type = type; +} + +void register_global_keys(HWND handle) +{ + auto * hotkey = &(plugin_cfg_gtk_global_hk.first); + auto _id = W_FIRST_GLOBAL_KEY_ID; + while (hotkey) + { + RegisterHotKey(handle, _id++, hotkey->mask, hotkey->key); + hotkey = hotkey->next; + } +} + +GdkFilterReturn w32_events_filter_first_everything(GdkXEvent * gdk_xevent, + GdkEvent * event, + gpointer user_data) +{ + auto msg = reinterpret_cast(gdk_xevent); + if (!msg->hwnd) + { + AUDWARN("We have event but hwnd is null. This doesn't make sense."); + return GDK_FILTER_CONTINUE; + } + // log_win_msg(msg->message); + if (msg->message == WM_SHOWWINDOW) + { + if (msg->wParam == TRUE) + { + auto event_window = WindowsWindow::get_window_data(msg->hwnd); + AUDDBG("(CommingUp)TRUE %s; %s", + stringify_win_evt(msg->message).c_str(), + (static_cast(event_window)).c_str()); + if (event_window.is_main_window(true)) + { + event_window.WindowsWindow::main_window_hidden_ = false; + AUDDBG( + "TRANSFER EventReceivingWindow from %s to mainwindow HWND " + "%p", + static_cast(message_receiving_window).c_str(), + event_window.handle()); + ungrab_keys(); + message_receiving_window = std::move(event_window); + grab_keys_onto_window(); + } + } + return GDK_FILTER_CONTINUE; +} + +GdkFilterReturn w32_evts_filter(GdkXEvent * gdk_xevent, GdkEvent * event, + gpointer user_data) +{ + auto msg = reinterpret_cast(gdk_xevent); + // log_win_msg(msg->message); + if (msg->message == WM_SHOWWINDOW) + { + if (msg->wParam != TRUE) + { + AUDDBG("MainWindow is getting hidden. We need to recreate the " + "hooks.\n TRANSFER EventReceivingWindow from %s", + static_cast(message_receiving_window).c_str()); + WindowsWindow::main_window_hidden_ = true; + ungrab_keys(); + grab_keys(); + } + } + else if (msg->message == WM_COMMAND) + { + auto buttonId = static_cast(msg->wParam & 0xFFFF); + AUDDBG("Clicked button with ID: %d", buttonId); + switch (buttonId) + { + case W_KEY_ID_PLAY: + handle_keyevent(EVENT_PAUSE); + break; + case W_KEY_ID_NEXT: + handle_keyevent(EVENT_NEXT_TRACK); + break; + case W_KEY_ID_PREV: + handle_keyevent(EVENT_PREV_TRACK); + break; + } + return GDK_FILTER_REMOVE; + } + else if (msg->message == WM_HOTKEY) + { + auto k_id = LOWORD(msg->wParam); + AUDDBG("Global hotkey: %du", k_id); + // Max 20 HKs + if (k_id >= W_FIRST_GLOBAL_KEY_ID && k_id < W_FIRST_GLOBAL_KEY_ID + 20) + { + auto idx = k_id - W_FIRST_GLOBAL_KEY_ID; + auto * config = &plugin_cfg_gtk_global_hk.first; + while (idx--) + { + config = config->next; + } + if (handle_keyevent(config->event)) + { + return GDK_FILTER_REMOVE; + } + } + } + return GDK_FILTER_CONTINUE; +} + +void register_hotkeys_with_available_window() +{ + assert(static_cast(message_receiving_window)); + auto * gdkwin = message_receiving_window.gdk_window(); + if (!gdkwin) + { + AUDINFO("HWND doesn't have associated gdk window. Cannot setup global " + "hotkeys."); + return; + } + gdk_window_add_filter(gdkwin, w32_evts_filter, nullptr); + register_global_keys(message_receiving_window.handle()); +} + +void release_filter() +{ + if (!message_receiving_window) + { + return; + } + gdk_window_remove_filter(message_receiving_window.gdk_window(), + w32_evts_filter, nullptr); + message_receiving_window = nullptr; +} + +gboolean window_created_callback(gpointer user_data) +{ + AUDDBG("Window created. Do real stuff."); + system_up_and_running = true; + gdk_window_add_filter(nullptr, w32_events_filter_first_everything, nullptr); + grab_keys(); + return G_SOURCE_REMOVE; +} + +void win_init() { g_idle_add(&window_created_callback, nullptr); } + +void Hotkey::key_to_string(int key, char ** out_keytext) +{ + // Special handling for most OEM keys - they may depend on language or + // keyboard? + switch (key) + { + case VK_OEM_NEC_EQUAL: + // case VK_OEM_FJ_JISHO: (note: same as EQUAL) + case VK_OEM_FJ_MASSHOU: + case VK_OEM_FJ_TOUROKU: + case VK_OEM_FJ_LOYA: + case VK_OEM_FJ_ROYA: + case VK_OEM_1: + case VK_OEM_2: + case VK_OEM_3: + case VK_OEM_4: + case VK_OEM_5: + case VK_OEM_6: + case VK_OEM_7: + case VK_OEM_8: + case VK_OEM_AX: + case VK_OEM_102: + case VK_OEM_RESET: + case VK_OEM_JUMP: + case VK_OEM_PA1: + case VK_OEM_PA2: + case VK_OEM_PA3: + case VK_OEM_WSCTRL: + case VK_OEM_CUSEL: + case VK_OEM_ATTN: + case VK_OEM_FINISH: + case VK_OEM_COPY: + case VK_OEM_AUTO: + case VK_OEM_ENLW: + case VK_OEM_BACKTAB: + case VK_OEM_CLEAR: + { + auto win_str = VirtualKeyCodeToStringMethod2(key); + if (!win_str.empty()) + { + *out_keytext = g_strdup(win_str.c_str()); + return; + } + } + } + *out_keytext = g_strdup(WIN_VK_NAMES[key]); +} + +char * Hotkey::create_human_readable_keytext(const char * const keytext, + int key, int mask) +{ + const auto w_mask = static_cast(mask); + std::ostringstream produced; + // "Control", "Shift", "Alt", "Win" + if (w_mask & static_cast(MOD_CONTROL)) + { + produced << "Control + "; + } + if (w_mask & static_cast(MOD_SHIFT)) + { + produced << "Shift + "; + } + if (w_mask & static_cast(MOD_ALT)) + { + produced << "Alt + "; + } + if (w_mask & static_cast(MOD_WIN)) + { + produced << "Win + "; + } + produced << keytext; + + AUDDBG(produced.str().c_str()); + return g_strdup(produced.str().c_str()); +} +void grab_keys_onto_window() +{ + AUDDBG("Will hook together the window of kind %s\n :%s", + message_receiving_window.kind_string(), + static_cast(message_receiving_window).c_str()); + if (message_receiving_window.kind() == AudaciousWindowKind::NONE || + message_receiving_window.kind() == AudaciousWindowKind::UNKNOWN) + { + AUDERR("Win API did not find the window to receive messages. Global " + "hotkeys are disabled!"); + message_receiving_window = nullptr; + return; + } + register_hotkeys_with_available_window(); +} +void grab_keys() +{ + if (!system_up_and_running) + { + return; + } + AUDDBG("Grabbing ..."); + + message_receiving_window = WindowsWindow::find_message_receiver_window(); + grab_keys_onto_window(); +} +void ungrab_keys() +{ + AUDDBG("Releasing ..."); + if (message_receiving_window) + { + auto * hotkey = &(plugin_cfg_gtk_global_hk.first); + auto _id = W_FIRST_GLOBAL_KEY_ID; + while (hotkey) + { + UnregisterHotKey(message_receiving_window.handle(), _id++); + hotkey = hotkey->next; + } + } + release_filter(); +} \ No newline at end of file diff --git a/src/hotkeyw32/windows_key_str_map.h b/src/hotkeyw32/windows_key_str_map.h new file mode 100644 index 0000000000..61c495d8d5 --- /dev/null +++ b/src/hotkeyw32/windows_key_str_map.h @@ -0,0 +1,292 @@ +/* + * windows_key_str_map.h + * Audacious + * + * Copyright (C) 2005-2020 Audacious team + * + * XMMS - Cross-platform multimedia player + * Copyright (C) 1998-2003 Peter Alm, Mikael Alm, Olle Hallnas, + * Thomas Nilsson and 4Front Technologies + * Copyright (C) 1999-2003 Haavard Kvaalen + * + * This program 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; under version 3 of the License. + * + * This program 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 this program. If not, see . + * + * The Audacious team does not consider modular code linking to + * Audacious or using our public API to be a derived work. + */ + +#ifndef AUDACIOUS_PLUGINS_WINDOWS_KEY_STRINGS_HPP +#define AUDACIOUS_PLUGINS_WINDOWS_KEY_STRINGS_HPP + +// https://docs.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes +// As defined in WinUser.h + +constexpr const char * const WIN_VK_NAMES[] = { + "UNKNOWN_KEY: 0", + "LBUTTON", + "RBUTTON", + "Scroll Lock", + "MBUTTON", + "XBUTTON1", + "XBUTTON2", + "UNKNOWN_KEY: 7", + "Backspace", + "Tab", + "UNKNOWN_KEY: 10", + "UNKNOWN_KEY: 11", + "CLEAR", + "Enter", + "UNKNOWN_KEY: 14", + "UNKNOWN_KEY: 15", + "Shift", + "Ctrl", + "Alt", + "PAUSE", + "Caps Lock", + "KANA", + "UNKNOWN_KEY: 22", + "JUNJA", + "FINAL", + "HANJA", + "UNKNOWN_KEY: 26", + "Esc", + "CONVERT", + "NONCONVERT", + "ACCEPT", + "MODECHANGE", + "Space", + "Page Up", + "Page Down", + "End", + "Home", + "Left", + "Up", + "Right", + "Down", + "SELECT", + "PRINT", + "EXECUTE", + "SNAPSHOT", + "Insert", + "Delete", + "HELP", + "0", + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + "UNKNOWN_KEY: 58", + "UNKNOWN_KEY: 59", + "UNKNOWN_KEY: 60", + "UNKNOWN_KEY: 61", + "UNKNOWN_KEY: 62", + "UNKNOWN_KEY: 63", + "UNKNOWN_KEY: 64", + "A", + "B", + "C", + "D", + "E", + "F", + "G", + "H", + "I", + "J", + "K", + "L", + "M", + "N", + "O", + "P", + "Q", + "R", + "S", + "T", + "U", + "V", + "W", + "X", + "Y", + "Z", + "LWIN", + "RWIN", + "APPS", + "UNKNOWN_KEY: 94", + "SLEEP", + "Num 0", + "Num 1", + "Num 2", + "Num 3", + "Num 4", + "Num 5", + "Num 6", + "Num 7", + "Num 8", + "Num 9", + "Num *", + "Num +", + "UNKNOWN_KEY: 108", + "Num -", + "Num Del", + "Num /", + "F1", + "F2", + "F3", + "F4", + "F5", + "F6", + "F7", + "F8", + "F9", + "F10", + "F11", + "F12", + "F13", + "F14", + "F15", + "F16", + "F17", + "F18", + "F19", + "F20", + "F21", + "F22", + "F23", + "F24", + "NAVIGATION_VIEW", + "NAVIGATION_MENU", + "NAVIGATION_UP", + "NAVIGATION_DOWN", + "NAVIGATION_LEFT", + "NAVIGATION_RIGHT", + "NAVIGATION_ACCEPT", + "NAVIGATION_CANCEL", + "Num Lock", + "Scroll Lock", + "OEM_NEC_EQUAL", + "OEM_FJ_MASSHOU", + "OEM_FJ_TOUROKU", + "OEM_FJ_LOYA", + "OEM_FJ_ROYA", + "UNKNOWN_KEY: 151", + "UNKNOWN_KEY: 152", + "UNKNOWN_KEY: 153", + "UNKNOWN_KEY: 154", + "UNKNOWN_KEY: 155", + "UNKNOWN_KEY: 156", + "UNKNOWN_KEY: 157", + "UNKNOWN_KEY: 158", + "UNKNOWN_KEY: 159", + "Left Shift", + "Right Shift", + "Left Ctrl", + "Right Control", + "Left Alt", + "Right Alt", + "BROWSER_BACK", + "BROWSER_FORWARD", + "BROWSER_REFRESH", + "BROWSER_STOP", + "BROWSER_SEARCH", + "BROWSER_FAVORITES", + "BROWSER_HOME", + "VOLUME_MUTE", + "VOLUME_DOWN", + "VOLUME_UP", + "MEDIA_NEXT_TRACK", + "MEDIA_PREV_TRACK", + "MEDIA_STOP", + "MEDIA_PLAY_PAUSE", + "LAUNCH_MAIL", + "LAUNCH_MEDIA_SELECT", + "LAUNCH_APP1", + "LAUNCH_APP2", + "UNKNOWN_KEY: 184", + "UNKNOWN_KEY: 185", + "OEM_1", + "OEM_PLUS", + "OEM_COMMA", + "OEM_MINUS", + "OEM_PERIOD", + "OEM_2", + "OEM_3", + "UNKNOWN_KEY: 193", + "F15", + "GAMEPAD_A", + "GAMEPAD_B", + "GAMEPAD_X", + "GAMEPAD_Y", + "GAMEPAD_RIGHT_SHOULDER", + "GAMEPAD_LEFT_SHOULDER", + "GAMEPAD_LEFT_TRIGGER", + "GAMEPAD_RIGHT_TRIGGER", + "GAMEPAD_DPAD_UP", + "GAMEPAD_DPAD_DOWN", + "GAMEPAD_DPAD_LEFT", + "GAMEPAD_DPAD_RIGHT", + "GAMEPAD_MENU", + "GAMEPAD_VIEW", + "GAMEPAD_LEFT_THUMBSTICK_BUTTON", + "GAMEPAD_RIGHT_THUMBSTICK_BUTTON", + "GAMEPAD_LEFT_THUMBSTICK_UP", + "GAMEPAD_LEFT_THUMBSTICK_DOWN", + "GAMEPAD_LEFT_THUMBSTICK_RIGHT", + "GAMEPAD_LEFT_THUMBSTICK_LEFT", + "GAMEPAD_RIGHT_THUMBSTICK_UP", + "GAMEPAD_RIGHT_THUMBSTICK_DOWN", + "GAMEPAD_RIGHT_THUMBSTICK_RIGHT", + "GAMEPAD_RIGHT_THUMBSTICK_LEFT", + "OEM_4", + "OEM_5", + "OEM_6", + "OEM_7", + "OEM_8", + "UNKNOWN_KEY: 224", + "OEM_AX", + "OEM_102", + "ICO_HELP", + "ICO_00", + "PROCESSKEY", + "ICO_CLEAR", + "PACKET", + "UNKNOWN_KEY: 232", + "OEM_RESET", + "OEM_JUMP", + "OEM_PA1", + "OEM_PA2", + "OEM_PA3", + "OEM_WSCTRL", + "OEM_CUSEL", + "OEM_ATTN", + "OEM_FINISH", + "OEM_COPY", + "OEM_AUTO", + "OEM_ENLW", + "OEM_BACKTAB", + "ATTN", + "CRSEL", + "EXSEL", + "EREOF", + "PLAY", + "ZOOM", + "NONAME", + "PA1", + "OEM_CLEAR", + "UNKNOWN_KEY: 255"}; + +#endif // AUDACIOUS_PLUGINS_WINDOWS_KEY_STRINGS_HPP diff --git a/src/hotkeyw32/windows_window.cc b/src/hotkeyw32/windows_window.cc new file mode 100644 index 0000000000..a317e00bfd --- /dev/null +++ b/src/hotkeyw32/windows_window.cc @@ -0,0 +1,316 @@ +/* + * windows_window.cc + * Audacious + * + * Copyright (C) 2005-2020 Audacious team + * + * XMMS - Cross-platform multimedia player + * Copyright (C) 1998-2003 Peter Alm, Mikael Alm, Olle Hallnas, + * Thomas Nilsson and 4Front Technologies + * Copyright (C) 1999-2003 Haavard Kvaalen + * + * This program 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; under version 3 of the License. + * + * This program 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 this program. If not, see . + * + * The Audacious team does not consider modular code linking to + * Audacious or using our public API to be a derived work. + */ + +#include "windows_window.h" + +#include +#include +#include +#include +#include +#include + +class Utf16CharArrConverter +{ + gchar * converted{nullptr}; + +public: + Utf16CharArrConverter(const wchar_t * const input_w_str) + : converted(reinterpret_cast( + g_utf16_to_utf8(reinterpret_cast(input_w_str), + -1, nullptr, nullptr, nullptr))) + { + } + ~Utf16CharArrConverter() + { + if (converted) + { + g_free(converted); + } + }; + operator std::string() + { // NOLINT(google-explicit-constructor) + return std::string(converted); + } +}; + +template +std::string to_hex_string(T integer) +{ + std::ostringstream os; + os << "0x" << std::setw(4) << std::setfill('0') << std::hex << integer; + return os.str(); +} + +std::string print_wins(const std::vector & wins) +{ + std::string printer; + for (auto & w : wins) + { + printer.append("\n ").append(static_cast(w)); + } + return printer; +} + +WindowsWindow::WindowsWindow(HWND handle, std::string className, + std::string winHeader, AudaciousWindowKind kind) + : handle_(handle), class_name_(std::move(className)), + win_header_(std::move(winHeader)), kind_(kind) +{ +} +std::string WindowsWindow::translated_title() +{ + // get "%s - " + std::string org = N_("%s - Audacious"); + // erase %s + org.erase(0, 2); + return org; +} +bool WindowsWindow::is_main_window(bool allow_hidden) const +{ + if (main_window_hidden_ && !allow_hidden) + return false; + bool returning = + (class_name_ == "gdkWindowToplevel" && [](const std::string & title) { + return title == N_("Audacious") || + title.find(translated_title()) != std::string::npos || + title == N_("Buffering ..."); // msgid "%s - Audacious" + }(win_header_)); + if (returning) + { + kind_ = AudaciousWindowKind::MAIN_WINDOW; + } + return returning; +} +WindowsWindow::operator std::string() const +{ + if (!handle_) + { + return "INVALID_WINDOW"; + } + std::string printer; + printer.append(std::to_string(reinterpret_cast(handle_))) + .append("|") + .append(to_hex_string(handle_)) + .append("(gdk:") + .append(std::to_string(reinterpret_cast(gdk_window()))) + .append("|") + .append(to_hex_string(gdk_window())) + .append(") ") + .append(class_name_) + .append(": ") + .append(win_header_); + return printer; +} +WindowsWindow WindowsWindow::get_window_data(HWND pHwnd) +{ + wchar_t char_buf[311]; + auto num_char = GetClassNameW(pHwnd, char_buf, _countof(char_buf)); + if (!num_char) + { + AUDDBG("WinApiError (code %ld): cannot get ClassName for %p.\n", + GetLastError(), pHwnd); + return {}; + } + std::string w_class_name = Utf16CharArrConverter(char_buf); + num_char = GetWindowTextW(pHwnd, char_buf, _countof(char_buf)); + if (!num_char) + { + AUDINFO("WinApiError (code %ld) getting WindowHeader. No header " + "for %p. Ignoring this window.\n", + GetLastError(), pHwnd); + char_buf[0] = 0; + } + return {pHwnd, std::move(w_class_name), + char_buf[0] ? Utf16CharArrConverter(char_buf) + : std::string{"[NO_TITLE]"}, + AudaciousWindowKind::UNKNOWN}; +} + +struct EnumWindowsCallbackArgs +{ + explicit EnumWindowsCallbackArgs(DWORD p) : pid(p) {} + const DWORD pid; + std::vector handles; + + void add_window(HWND pHwnd) + { + handles.emplace_back(WindowsWindow::get_window_data(pHwnd)); + } +}; + +static BOOL CALLBACK EnumWindowsCallback(HWND hnd, LPARAM lParam) +{ + // Get pointer to created callback object for storing data. + auto * args = reinterpret_cast(lParam); + // Callback result are all windows (not only my program). I filter by PID / + // thread ID. + DWORD windowPID; + GetWindowThreadProcessId(hnd, &windowPID); + // Compare to the one we have stored in our callbaack structure. + if (windowPID == args->pid) + { + args->add_window(hnd); + } + return TRUE; +} + +std::vector get_this_app_windows() +{ + // Create object that will hold a result. + EnumWindowsCallbackArgs args(GetCurrentProcessId()); + // AUDERR("Testing getlasterror: %ld\n.", GetLastError()); + // Call EnumWindows and pass pointer to function and created callback + // structure. + if (EnumWindows(&EnumWindowsCallback, (LPARAM)&args) == FALSE) + { + // If the call fails, return empty vector + AUDERR("WinApiError (code %ld). Cannot get windows, hotkey plugin " + "won't work.\n", + GetLastError()); + return std::vector{}; + } +#ifndef NDEBUG + AUDDBG("Aud has %zu windows: %s", args.handles.size(), + print_wins(args.handles).c_str()); +#endif + return std::move(args.handles); +} +WindowsWindow WindowsWindow::find_message_receiver_window() +{ + auto ws = get_this_app_windows(); + auto main_win = std::find_if(ws.begin(), ws.end(), [](WindowsWindow & itm) { + return itm.is_main_window(); + }); + if (main_win != ws.end()) + { + main_win->kind_ = AudaciousWindowKind::MAIN_WINDOW; + return std::move(*main_win); + } + main_win = + std::find_if(ws.begin(), ws.end(), [](const WindowsWindow & itm) { + return itm.gdk_window() != nullptr; + }); + if (main_win != ws.end()) + { + main_win->kind_ = + AudaciousWindowKind::TASKBAR_WITH_WINHANDLE_AND_GDKHANDLE; + return std::move(*main_win); + } + main_win = + std::find_if(ws.begin(), ws.end(), [](const WindowsWindow & itm) { + return itm.class_name_ == "gtkstatusicon-observer"; + }); + if (main_win == ws.end()) + { + AUDDBG("No proper window found. Giving up."); + return {nullptr, {}, {}, AudaciousWindowKind::NONE}; + } + main_win->kind_ = AudaciousWindowKind::GTKSTATUSICON_OBSERVER; + return std::move(*main_win); +} +WindowsWindow::operator bool() const { return handle_; } +GdkWindow * WindowsWindow::gdk_window() const +{ + if (!gdk_window_) + { + gdk_window_ = reinterpret_cast( + gdk_win32_handle_table_lookup(handle_)); + } + return gdk_window_; +} +void WindowsWindow::unref() +{ + handle_ = nullptr; + gdk_window_ = nullptr; +} + +#define ENUM_KIND_CASE(item) \ + case item: \ + return #item; + +const char * WindowsWindow::kind_string() +{ + switch (kind_) + { + ENUM_KIND_CASE(AudaciousWindowKind::MAIN_WINDOW) + ENUM_KIND_CASE(AudaciousWindowKind::UNKNOWN) + ENUM_KIND_CASE(AudaciousWindowKind::NONE) + ENUM_KIND_CASE(AudaciousWindowKind::GTKSTATUSICON_OBSERVER) + ENUM_KIND_CASE( + AudaciousWindowKind::TASKBAR_WITH_WINHANDLE_AND_GDKHANDLE) + } + return "bad WindowsWindow ; unknown"; +} + +std::string stringify_win_evt(UINT id) +{ + switch (id) + { + ENUM_KIND_CASE(WM_DESTROY) + ENUM_KIND_CASE(WM_CREATE) + ENUM_KIND_CASE(WM_ACTIVATE) + ENUM_KIND_CASE(WM_ENABLE) + ENUM_KIND_CASE(WM_SHOWWINDOW) + ENUM_KIND_CASE(WM_WINDOWPOSCHANGING) + ENUM_KIND_CASE(WM_WINDOWPOSCHANGED) + ENUM_KIND_CASE(WM_NCACTIVATE) + ENUM_KIND_CASE(WM_NCPAINT) + ENUM_KIND_CASE(WM_NCCREATE) + ENUM_KIND_CASE(WM_ACTIVATEAPP) + ENUM_KIND_CASE(WM_KILLFOCUS) + ENUM_KIND_CASE(WM_SETFOCUS) + ENUM_KIND_CASE(WM_IME_SETCONTEXT) + ENUM_KIND_CASE(WM_IME_NOTIFY) + ENUM_KIND_CASE(WM_GETICON) + ENUM_KIND_CASE(WM_PAINT) + ENUM_KIND_CASE(WM_SETCURSOR) + ENUM_KIND_CASE(WM_MOUSEMOVE) + ENUM_KIND_CASE(WM_NCMOUSEMOVE) + ENUM_KIND_CASE(WM_MOUSEACTIVATE) + ENUM_KIND_CASE(WM_NCLBUTTONDOWN) + ENUM_KIND_CASE(WM_CAPTURECHANGED) + ENUM_KIND_CASE(WM_SYSCOMMAND) + ENUM_KIND_CASE(WM_CLOSE) + ENUM_KIND_CASE(WM_MOVING) + ENUM_KIND_CASE(WM_ERASEBKGND) + ENUM_KIND_CASE(WM_EXITMENULOOP) + ENUM_KIND_CASE(WM_NCMOUSELEAVE) + ENUM_KIND_CASE(WM_NCHITTEST) + ENUM_KIND_CASE(WM_EXITSIZEMOVE) + ENUM_KIND_CASE(WM_ENTERSIZEMOVE) + ENUM_KIND_CASE(WM_MOUSELEAVE) + ENUM_KIND_CASE(WM_PARENTNOTIFY) + ENUM_KIND_CASE(WM_NCCALCSIZE) + ENUM_KIND_CASE(WM_GETMINMAXINFO) + ENUM_KIND_CASE(WM_QUERYOPEN) + default: + return "Unknown msg: " + to_hex_string(id); + } +} + +bool WindowsWindow::main_window_hidden_{false}; \ No newline at end of file diff --git a/src/hotkeyw32/windows_window.h b/src/hotkeyw32/windows_window.h new file mode 100644 index 0000000000..abb14af1c4 --- /dev/null +++ b/src/hotkeyw32/windows_window.h @@ -0,0 +1,106 @@ +/* + * windows_window.h + * Audacious + * + * Copyright (C) 2005-2020 Audacious team + * + * XMMS - Cross-platform multimedia player + * Copyright (C) 1998-2003 Peter Alm, Mikael Alm, Olle Hallnas, + * Thomas Nilsson and 4Front Technologies + * Copyright (C) 1999-2003 Haavard Kvaalen + * + * This program 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; under version 3 of the License. + * + * This program 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 this program. If not, see . + * + * The Audacious team does not consider modular code linking to + * Audacious or using our public API to be a derived work. + */ + +#ifndef _AUD_PLUGINS_HOTKEYW32_WINDOWS_WINDOW_H_INCLUDED_ +#define _AUD_PLUGINS_HOTKEYW32_WINDOWS_WINDOW_H_INCLUDED_ + +#include +#include +#include + +enum class AudaciousWindowKind +{ + NONE, + MAIN_WINDOW, + TASKBAR_WITH_WINHANDLE_AND_GDKHANDLE, + GTKSTATUSICON_OBSERVER, + UNKNOWN +}; + +struct WindowsWindow +{ + static bool main_window_hidden_; + WindowsWindow() = default; + ~WindowsWindow() = default; + WindowsWindow(const WindowsWindow & right) = delete; + WindowsWindow & operator=(const WindowsWindow & right) = delete; + WindowsWindow & operator=(WindowsWindow && right) = default; + WindowsWindow(WindowsWindow && right) = default; + + WindowsWindow(HWND handle, std::string className, std::string winHeader, + AudaciousWindowKind kind); + static std::string translated_title(); + bool is_main_window(bool allow_hidden = false) const; + explicit operator std::string() const; + static WindowsWindow get_window_data(HWND pHwnd); + static WindowsWindow find_message_receiver_window(); + + operator bool() const; // NOLINT(google-explicit-constructor) + // clang-format off + void unref(); + WindowsWindow& operator=(std::nullptr_t dummy) { unref(); return *this; } + // clang-format on + + HWND handle() const { return handle_; } + const std::string & class_name() const { return class_name_; } + const std::string & win_header() const { return win_header_; } + AudaciousWindowKind kind() const { return kind_; } + GdkWindow * gdk_window() const; + + const char * kind_string(); + +private: + HWND handle_{}; + std::string class_name_{}; + std::string win_header_{}; + mutable AudaciousWindowKind kind_{}; + mutable GdkWindow * gdk_window_{}; +}; + +struct MainWindowSearchFilterData +{ + gpointer window_; + void * function_ptr_; + + MainWindowSearchFilterData(gpointer window, void * functionPtr) + : window_(window), function_ptr_(functionPtr) + { + } + + MainWindowSearchFilterData(const MainWindowSearchFilterData &) = delete; + MainWindowSearchFilterData(MainWindowSearchFilterData &&) = delete; + MainWindowSearchFilterData & + operator=(const MainWindowSearchFilterData &) = delete; + MainWindowSearchFilterData & + operator=(MainWindowSearchFilterData &&) = delete; + ~MainWindowSearchFilterData() = default; +}; + +std::vector get_this_app_windows(); +std::string stringify_win_evt(UINT id); + +#endif \ No newline at end of file