FIX(client): Migrate Windows shortcuts from versions older than 1.4.0 properly

After 3dc7b00d09, shortcuts saved in the old format would just show up as "Unknown" and not work.

This commit implements a proper migration mechanism, making the shortcuts work without any manual steps.
Mice, keyboards, Xbox (XInput) controllers and Logitech G-keys devices are supported.
Unfortunately shortcuts will have to be registered again for DirectInput devices such as controllers.

The migration is performed silently, unless not 100% successful.
In that case a message box appears informing the user about how many shortcuts could not be migrated.

Please note that running a version of Mumble that uses the old format after the migration results in corruption.
As a result, shortcuts would have to be registered again.

Fixes #5025.
This commit is contained in:
Davide Beatrici 2021-07-25 00:34:27 +02:00
parent a4f41775e8
commit 0e17c5394c
3 changed files with 134 additions and 14 deletions

View File

@ -16,10 +16,10 @@
#include <codecvt>
#include <iomanip>
#include <sstream>
#include <QTimer>
#include <sstream>
#include <QUuid>
extern "C" {
// clang-format off
@ -208,6 +208,103 @@ void GlobalShortcutWin::registerMetaTypes() {
}
}
QList< Shortcut > GlobalShortcutWin::migrateSettings(const QList< Shortcut > &oldShortcuts) {
constexpr QUuid keyboardUuid(0x6F1D2B61, 0xD5A0, 0x11CF, 0xBF, 0xC7, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00);
constexpr QUuid mouseUuid(0x6F1D2B60, 0xD5A0, 0x11CF, 0xBF, 0xC7, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00);
constexpr QUuid xinputUuid(0xCA3937E3, 0x640C, 0x4D9E, 0x9E, 0xF3, 0x90, 0x3F, 0x8B, 0x4F, 0xBC, 0xAB);
constexpr QUuid gkeyKeyboardUuid(0x153E64E6, 0x98C8, 0x4E, 0x03, 0x80EF, 0x5F, 0xFD, 0x33, 0xD2, 0x5B, 0x8A);
constexpr QUuid gkeyMouseUuid(0xC41E60AF, 0x9022, 0x46CF, 0xBC, 0x39, 0x37, 0x98, 0x10, 0x82, 0xD7, 0x16);
QList< Shortcut > newShortcuts;
for (Shortcut shortcut : oldShortcuts) {
bool ok = true;
for (QVariant &button : shortcut.qlButtons) {
if (!button.isValid()) {
// This happens when the user runs a version that uses
// the old format after the migration is performed.
ok = false;
break;
}
if (button.userType() == qMetaTypeId< InputHid >() || button.userType() == qMetaTypeId< InputKeyboard >()
|| button.userType() == qMetaTypeId< InputMouse >() || button.userType() == qMetaTypeId< InputXinput >()
|| button.userType() == qMetaTypeId< InputGkey >()) {
// Already in the new format.
continue;
}
const QVariantList entries = button.toList();
if (entries.size() < 2) {
ok = false;
break;
}
auto value = entries.at(0).toUInt(&ok);
if (!ok) {
break;
}
const auto uuid = entries.at(1).toUuid();
if (uuid == keyboardUuid) {
InputKeyboard input;
input.code = (value & ~0x8000U) >> 8;
input.e0 = value & 0x8000U;
// With DirectInput the extended bit is:
// - Set for the Pause key.
// - Unset for the Numlock key.
// With raw input it's the opposite.
if (input.code == 0x45) {
input.e0 = !input.e0;
}
button = QVariant::fromValue(input);
} else if (uuid == mouseUuid) {
value >>= 8;
if (value < 3 || value > 7) {
ok = false;
break;
}
button = QVariant::fromValue(static_cast< InputMouse >(value - 2));
#ifdef USE_XBOXINPUT
} else if (uuid == xinputUuid) {
InputXinput input;
input.device = (value >> 24) & 0xFF;
input.code = value & 0x00FFFFFF;
button = QVariant::fromValue(input);
#endif
#ifdef USE_GKEY
} else if (uuid == gkeyKeyboardUuid) {
InputGkey input = {};
input.keyboard = true;
input.mode = value >> 16;
input.button = value & 0xFFFF;
button = QVariant::fromValue(input);
} else if (uuid == gkeyMouseUuid) {
InputGkey input = {};
input.button = value;
button = QVariant::fromValue(input);
#endif
} else {
ok = false;
break;
}
}
if (ok) {
newShortcuts << shortcut;
}
}
return newShortcuts;
}
GlobalShortcutWin::GlobalShortcutWin()
#ifdef USE_XBOXINPUT
: m_xinputDevices(0), m_xinputLastPacket()

View File

@ -26,6 +26,10 @@ class GlobalShortcutWin : public GlobalShortcutEngine {
public:
static void registerMetaTypes();
/// @param oldShortcuts List of shortcuts to migrate.
/// @returns List of shortcuts in the new format.
static QList< Shortcut > migrateSettings(const QList< Shortcut > &oldShortcuts);
/// Inject a native Windows raw input message into GlobalShortcutWin's
/// thread. This method is meant to be called from the main thread
/// to pass native Windows keyboard messages to GlobalShortcutWin.

View File

@ -12,6 +12,9 @@
#include "Cert.h"
#include "Database.h"
#include "DeveloperConsole.h"
#ifdef Q_OS_WIN
# include "GlobalShortcut_win.h"
#endif
#include "LCD.h"
#include "Log.h"
#include "LogEmitter.h"
@ -706,19 +709,35 @@ int main(int argc, char **argv) {
// Configuration updates
bool runaudiowizard = false;
if (Global::get().s.uiUpdateCounter == 0) {
// Previous version was an pre 1.2.3 release or this is the first run
runaudiowizard = true;
switch (Global::get().s.uiUpdateCounter) {
case 0:
// Previous version was an pre 1.2.3 release or this is the first run
runaudiowizard = true;
// Fallthrough
case 1:
// Previous versions used old idle action style, convert it
if (Global::get().s.iIdleTime == 5 * 60) { // New default
Global::get().s.iaeIdleAction = Settings::Nothing;
} else {
Global::get().s.iIdleTime = 60 * qRound(Global::get().s.iIdleTime / 60.); // Round to minutes
Global::get().s.iaeIdleAction = Settings::Deafen; // Old behavior
}
// Fallthrough
#ifdef Q_OS_WIN
case 2: {
QList< Shortcut > &shortcuts = Global::get().s.qlShortcuts;
const QList< Shortcut > migratedShortcuts = GlobalShortcutWin::migrateSettings(shortcuts);
if (shortcuts.size() > migratedShortcuts.size()) {
const uint32_t num = shortcuts.size() - migratedShortcuts.size();
QMessageBox::warning(
nullptr, QObject::tr("Shortcuts migration incomplete"),
QObject::tr("Unfortunately %1 shortcut(s) could not be migrated.\nYou can register them again.")
.arg(num));
}
} else if (Global::get().s.uiUpdateCounter == 1) {
// Previous versions used old idle action style, convert it
if (Global::get().s.iIdleTime == 5 * 60) { // New default
Global::get().s.iaeIdleAction = Settings::Nothing;
} else {
Global::get().s.iIdleTime = 60 * qRound(Global::get().s.iIdleTime / 60.); // Round to minutes
Global::get().s.iaeIdleAction = Settings::Deafen; // Old behavior
shortcuts = migratedShortcuts;
}
#endif
}
if (runaudiowizard) {
@ -727,7 +746,7 @@ int main(int argc, char **argv) {
delete aw;
}
Global::get().s.uiUpdateCounter = 2;
Global::get().s.uiUpdateCounter = 3;
if (!CertWizard::validateCert(Global::get().s.kpCertificate)) {
QFile qf(qdCert.absoluteFilePath(QLatin1String("MumbleAutomaticCertificateBackup.p12")));