Skip to content

Commit

Permalink
Merge pull request #9 from GaZaTu/feature/custom-hotkeys-and-more
Browse files Browse the repository at this point in the history
LOL
  • Loading branch information
GaZaTu authored Oct 24, 2023
2 parents cda1692 + ac94e65 commit 901acdf
Show file tree
Hide file tree
Showing 10 changed files with 207 additions and 60 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
options: --privileged
strategy:
matrix:
image: ['ubuntu:23.04', 'ubuntu:22.10', 'ubuntu:22.04', 'ubuntu:20.04', 'debian:12', 'debian:11', 'debian:10', 'opensuse/leap:15.4', 'fedora:38', 'fedora:37']
image: ['ubuntu:23.04', 'ubuntu:22.04', 'ubuntu:20.04', 'debian:12', 'debian:11', 'debian:10', 'opensuse/leap:15.4', 'fedora:40', 'fedora:39', 'fedora:38']
fail-fast: false

steps:
Expand Down
4 changes: 2 additions & 2 deletions .scripts/ibus-remove.sh
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
#!/bin/sh

rm -f /usr/share/ibus/component/im-emoji-picker-ibus.xml
rm -f /usr/share/ibus/component/ibusimemojipicker.xml

rm -f /usr/lib/ibus/im-emoji-picker-ibus
rm -f /usr/lib/ibus/ibusimemojipicker

rm -f /usr/share/icons/hicolor/32x32/apps/im-emoji-picker.png
touch /usr/share/icons/hicolor
Expand Down
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,14 @@ useSystemQtTheme=false
; (requires IMF restart)
windowOpacity=0.9

; Use something like the following to add custom hotkeys (target = the default key press as seen below):
; [customHotKeys]
; 1\sourceKeyChr=#
; 1\targetKeySeq=shift+tab
; size=1
[customHotKeys]
size=0

; The files to load emoji aliases from.
; Refer to src/res/aliases/github-emojis.ini for an example
; (requires IMF restart)
Expand Down Expand Up @@ -194,6 +202,7 @@ size=1
- `return` = write emoji to target input
- `shift+return` = write emoji to target input and close emoji picker
- `tab` = change view (MRU, List, Kaomoji)
- `shift+tab` = change view (MRU, List, Kaomoji) (reverse)
- `f4` = open settings file and close emoji picker

## Building 🤓
Expand Down
37 changes: 37 additions & 0 deletions src/EmojiPickerSettings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ void EmojiPickerSettings::writeDefaultsToDisk() {
s.useSystemEmojiFontWidthHeuristics(s.useSystemEmojiFontWidthHeuristics());
s.scaleFactor(s.scaleFactor());
s.saveKaomojiInMRU(s.saveKaomojiInMRU());
s.customHotKeys(s.customHotKeys());
}

EmojiPickerSettings::EmojiPickerSettings() : QSettings(QSettings::IniFormat, QSettings::UserScope, QCoreApplication::organizationName(), QCoreApplication::applicationName(), nullptr) {
Expand Down Expand Up @@ -199,6 +200,42 @@ void EmojiPickerSettings::saveKaomojiInMRU(bool saveKaomojiInMRU) {
setValue("saveKaomojiInMRU", saveKaomojiInMRU);
}

std::unordered_map<char, QKeySequence> EmojiPickerSettings::customHotKeys() {
std::unordered_map<char, QKeySequence> result;

int len = beginReadArray("customHotKeys");
for (int i = 0; i < len; i++) {
setArrayIndex(i);

auto keyValue = value("sourceKeyChr");
char key = 0;
if (keyValue.type() == QVariant::StringList) {
key = ',';
} else {
key = keyValue.toString().at(0).toLatin1();
}
auto target = QKeySequence{value("targetKeySeq").toString(), QKeySequence::PortableText};
result.emplace(key, target);
}
endArray();

return result;
}
void EmojiPickerSettings::customHotKeys(const std::unordered_map<char, QKeySequence>& customHotKeys) {
beginWriteArray("customHotKeys", customHotKeys.size());
int i = 0;
for (const auto& [key, target] : customHotKeys) {
setArrayIndex(i++);
if (key == ',') {
setValue("sourceKeyChr", QStringList{"", ""});
} else {
setValue("sourceKeyChr", QString::fromStdString(std::string{key}));
}
setValue("targetKeySeq", target.toString(QKeySequence::PortableText));
}
endArray();
}

EmojiPickerCache::EmojiPickerCache() : QSettings(path(), QSettings::IniFormat) {
}

Expand Down
4 changes: 4 additions & 0 deletions src/EmojiPickerSettings.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include "emojis.hpp"
#include <QFontMetrics>
#include <QSettings>
#include <unordered_map>
#include <utility>
#include <vector>

Expand Down Expand Up @@ -53,6 +54,9 @@ class EmojiPickerSettings : public QSettings {

bool saveKaomojiInMRU() const;
void saveKaomojiInMRU(bool saveKaomojiInMRU);

std::unordered_map<char, QKeySequence> customHotKeys();
void customHotKeys(const std::unordered_map<char, QKeySequence>& customHotKeys);
};

class EmojiPickerCache : public QSettings {
Expand Down
105 changes: 88 additions & 17 deletions src/EmojiPickerWindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@
#include <vector>
#include <QTextStream>
#include <QFile>
#include <unistd.h>
#include <mutex>
#include <condition_variable>

Emoji convertKaomojiToEmoji(const Kaomoji& kaomoji) {
return Emoji{kaomoji.name, kaomoji.text, -1};
Expand All @@ -32,9 +35,9 @@ void moveQWidgetToCenter(QWidget* window) {
}

QPoint createPointInScreen(QWidget* window, QRect newPoint) {
QPoint result{newPoint.x(), newPoint.y()};
result.setX(result.x() + newPoint.width());
result.setY(result.y() + newPoint.height());
QPoint result{0, 0};
result.setX(newPoint.x() + newPoint.width());
result.setY(newPoint.y() + newPoint.height());

QRect windowRect = window->geometry();

Expand All @@ -43,7 +46,6 @@ QPoint createPointInScreen(QWidget* window, QRect newPoint) {
if (!screen) {
return result;
}

QRect screenRect = screen->geometry();
#else
QRect screenRect = QApplication::desktop()->availableGeometry(result);
Expand Down Expand Up @@ -78,17 +80,19 @@ std::function<void()> resetInputMethodEngine = []() {
ThreadsafeQueue<std::shared_ptr<EmojiCommand>> emojiCommandQueue;

EmojiPickerWindow::EmojiPickerWindow() : QMainWindow() {
setFocusPolicy(Qt::NoFocus);
setAttribute(Qt::WA_ShowWithoutActivating);
setWindowFlags(Qt::Tool | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint | Qt::WindowDoesNotAcceptFocus);
setWindowIcon(QIcon(":/res/im-emoji-picker_72x72.png"));
setWindowOpacity(_settings.windowOpacity());
setFocusPolicy(Qt::NoFocus);
setAttribute(Qt::WA_ShowWithoutActivating);
setWindowTitle("im-emoji-picker");
setFixedSize(340, 190);

_searchContainerWidget->setLayout(_searchContainerLayout);
_searchContainerLayout->setStackingMode(QStackedLayout::StackAll);

_searchCompletion->setFocusPolicy(Qt::NoFocus);
_searchCompletion->setAttribute(Qt::WA_ShowWithoutActivating);
_searchCompletion->setReadOnly(true);
if (_settings.useSystemQtTheme()) {
_searchCompletion->setStyleSheet(_searchCompletion->styleSheet() + QString("background: #00000000;"));
Expand Down Expand Up @@ -513,8 +517,10 @@ void EmojiPickerWindow::reset() {
disable();
}

void EmojiPickerWindow::enable() {
moveQWidgetToCenter(this);
void EmojiPickerWindow::enable(bool resetPosition) {
if (resetPosition) {
moveQWidgetToCenter(this);
}

show();

Expand Down Expand Up @@ -570,7 +576,7 @@ void EmojiPickerWindow::setCursorLocation(const QRect* rect) {
newRect.setY((double)rect->y() / pixelRatio);
newRect.setWidth((double)rect->width() / pixelRatio);
newRect.setHeight((double)rect->height() / pixelRatio);
newRect.setWidth(0);
newRect.setWidth(0); // ????

QPoint newPoint = createPointInScreen(this, newRect);
// newPoint.setX((double)rect->x() / pixelRatio);
Expand All @@ -579,6 +585,44 @@ void EmojiPickerWindow::setCursorLocation(const QRect* rect) {
move(newPoint);
}

QKeyEvent* createKeyEventWithUserPreferences(QEvent::Type _type, int _key, Qt::KeyboardModifiers _modifiers, const QString& _text) {
static std::unordered_map<char, QKeySequence> customHotKeys; // lazy
static bool customHotKeysLoaded = false;
if (!customHotKeysLoaded) {
customHotKeysLoaded = true;

std::unique_ptr<QCoreApplication> dummyApp;
if (QCoreApplication::instance() == nullptr) {
int argc = 0;
char** argv = nullptr;
dummyApp = std::make_unique<QCoreApplication>(argc, argv);
}

customHotKeys = EmojiPickerSettings{}.customHotKeys();
}

for (const auto& [key, target] : customHotKeys) {
if (_text.at(0).toLatin1() == key) {
_key = target[0];
_modifiers = Qt::NoModifier;
if (target[0] & Qt::ShiftModifier) {
_modifiers |= Qt::ShiftModifier;
_key &= ~Qt::ShiftModifier;
}
if (target[0] & Qt::ControlModifier) {
_modifiers |= Qt::ControlModifier;
_key &= ~Qt::ControlModifier;
}
if (target[0] & Qt::AltModifier) {
_modifiers |= Qt::AltModifier;
_key &= ~Qt::AltModifier;
}
}
}

return new QKeyEvent(_type, _key, _modifiers, _text);
}

EmojiAction getEmojiActionForQKeyEvent(const QKeyEvent* event) {
// TODO: ctrl+w = delete word
// TODO: ctrl+d = select word
Expand Down Expand Up @@ -691,8 +735,10 @@ void EmojiPickerWindow::commitEmoji(const Emoji& emoji, bool isRealEmoji, bool c
}
}

void EmojiPickerWindow::processKeyEvent(const QKeyEvent* event) {
EmojiAction action = getEmojiActionForQKeyEvent(event);
void EmojiPickerWindow::processKeyEvent(const QKeyEvent* event, EmojiAction action) {
if (action == EmojiAction::INVALID) {
action = getEmojiActionForQKeyEvent(event);
}

switch (action) {
case EmojiAction::INVALID:
Expand Down Expand Up @@ -839,6 +885,23 @@ void loadScaleFactorFromSettings() {
}
}

std::mutex gui_mutex;
std::condition_variable gui_condition;
bool gui_is_active = false;

void gui_set_active(bool active) {
if (!active) {
// give Qt time to actually close the window before the Qt main thread is blocked
// (assuming 'EmojiCommandDisable' has been dispatched beforehand)
usleep(1000 /*us*/ * 128 /*ms*/);
}

std::unique_lock<decltype(gui_mutex)> lock{gui_mutex};
gui_is_active = active;
lock.unlock();
gui_condition.notify_one();
}

void gui_main(int argc, char** argv) {
QApplication::setOrganizationName(PROJECT_ORGANIZATION);
QApplication::setOrganizationDomain(PROJECT_ORGANIZATION);
Expand All @@ -858,17 +921,26 @@ void gui_main(int argc, char** argv) {

EmojiPickerWindow window;

// TODO: improve the following
QTimer emojiCommandProcessor;
QObject::connect(&emojiCommandProcessor, &QTimer::timeout, [&window]() {
QTimer commandProcessor;
commandProcessor.start(32 /*ms*/);
QObject::connect(&commandProcessor, &QTimer::timeout, [&commandProcessor, &window]() {
// block the entire Qt main thread if gui_is_active == false
std::unique_lock<decltype(gui_mutex)> lock{gui_mutex};
gui_condition.wait(lock, []() { return gui_is_active; });
lock.unlock();

std::shared_ptr<EmojiCommand> _command;
if (emojiCommandQueue.pop(_command)) {
if (auto command = std::dynamic_pointer_cast<EmojiCommandEnable>(_command)) {
window.commitText = command->commitText;
window.enable();
window.enable(command->resetPosition);

commandProcessor.setInterval(4 /*ms*/);
}
if (auto command = std::dynamic_pointer_cast<EmojiCommandDisable>(_command)) {
window.disable();

commandProcessor.setInterval(32 /*ms*/);
}
if (auto command = std::dynamic_pointer_cast<EmojiCommandReset>(_command)) {
window.reset();
Expand All @@ -877,11 +949,10 @@ void gui_main(int argc, char** argv) {
window.setCursorLocation(&*command->rect);
}
if (auto command = std::dynamic_pointer_cast<EmojiCommandProcessKeyEvent>(_command)) {
window.processKeyEvent(&*command->keyEvent);
window.processKeyEvent(&*command->keyEvent, command->action);
}
}
});
emojiCommandProcessor.start(5);

app.exec();
}
Loading

0 comments on commit 901acdf

Please sign in to comment.