이번편은 제가 개발직에 있으면서 나온 QML 버그를 하나 소개하려고 합니다.
MacOS에서 qml textfield component 사용시 문제가 되는 점이 하나 있었습니다. 바로 textfield에 echoMode의 옵션을 password로 주었고 해당 textfield가 활성화 되었을 경우 MacOS의 IME가 password 필드에 맞게 1byte 문자로만 활성화 되어야하는데 2byte이상의 다른 문자도 활성화되어 보안상의 문제가 발생하는 버그가 있습니다.

아래는 제가 버그 report한 url 입니다.
https://bugreports.qt.io/browse/QTBUG-106250
report는 끝났고 그럼 이제 이 버그에 대해 해결방법을 보여드리겠습니다.
예제입니다.
main.qml
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.12
import QtQuick.Layouts 1.12
Window {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
ColumnLayout {
anchors.fill: parent
anchors.margins: 10
spacing: 10
TextField {
width: 100
height: 50
placeholderText: "ID"
}
TextField {
width: 100
height: 50
echoMode: TextInput.Password
placeholderText: "PW"
}
}
}
UI에 id필드와 password필드 두개를 선언했습니다.
mainclass.h
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QObject>
#include "macinputcontext.h"
class MainClass : public QObject
{
Q_OBJECT
public:
explicit MainClass(QObject *parent = nullptr);
MainClass(QQmlApplicationEngine *engine, QGuiApplication *app);
~MainClass();
int load();
void updateTextInputContext(bool on);
signals:
public slots:
void onFocusObjectChanged(QObject *focusObject);
private:
QGuiApplication *_app;
QQmlApplicationEngine *_engine;
MacInputContext *_macInputContext;
};
#endif // MAINCLASS_H
mainclass.cpp
#include "mainclass.h"
#include <QMetaObject>
#include <QDebug>
#include <QLineEdit>
MainClass::MainClass(QObject *parent) : QObject(parent)
{
}
MainClass::MainClass(QQmlApplicationEngine *engine, QGuiApplication *app)
{
_app = app;
_engine = engine;
_macInputContext = new MacInputContext(this);
}
MainClass::~MainClass()
{
delete _macInputContext;
}
int MainClass::load()
{
const QUrl url(QStringLiteral("qrc:/main.qml"));
QObject::connect(_engine, &QQmlApplicationEngine::objectCreated,
_app, [url](QObject *obj, const QUrl &objUrl) {
if (!obj && url == objUrl)
QCoreApplication::exit(-1);
}, Qt::QueuedConnection);
QObject::connect(_app, &QGuiApplication::focusObjectChanged, this, &MainClass::onFocusObjectChanged);
_engine->load(url);
return _app->exec();
}
void MainClass::updateTextInputContext(bool on)
{
#ifdef Q_OS_MAC
_macInputContext->updateTextInputContext(on);
#endif
Q_UNUSED(on)
}
void MainClass::onFocusObjectChanged(QObject *focusObject)
{
if(!focusObject)
return;
const QMetaObject * metaObject = focusObject->metaObject();
int methodHintsindex = metaObject->indexOfProperty("inputMethodHints");
int echoModeIndex = metaObject->indexOfProperty("echoMode");
if(methodHintsindex == -1 || echoModeIndex == -1)
{
updateTextInputContext(false);
return ;
}
if(metaObject->property(methodHintsindex).read(focusObject) == Qt::ImhHiddenText)
{
updateTextInputContext(true);
return ;
}
else
{
int r = metaObject->property(echoModeIndex).read(focusObject).toInt();
if(r == QLineEdit::Password || r == QLineEdit::PasswordEchoOnEdit)
{
updateTextInputContext(true);
return ;
}
}
updateTextInputContext(false);
}
load() 함수안에 QObject::connect(_app, &QGuiApplication::focusObjectChanged, this, &MainClass::onFocusObjectChanged);
_engine->load(url); 코드를 보면 앱의 focusObject가 변경되면 onFocusObjectChanged 함수를 호출하게끔 signal slot을 연결했습니다.
slot함수인 onFocusObjectChanged를 보면 focus가 변한 object를 받아 해당 object가 textfield의 echoMode가 password필드인지 확인하여 처리할 수 있게 하는 함수입니다.
그리고 QLineEdit enum을 사용하기 위해 pro파일에 선언해줍니다.
macInputContext.pro
QT += widgets
이제 password필드라면 mac에서 1byte문자만 허용하게끔 처리하는 코드를 작성합니다.
macinputcontext.h
#ifndef MACINPUTCONTEXT_H
#define MACINPUTCONTEXT_H
#include <QObject>
class MacInputContext : public QObject
{
Q_OBJECT
public:
explicit MacInputContext(QObject *parent = nullptr);
void updateTextInputContext(bool on);
signals:
public slots:
};
#endif // MACINPUTCONTEXT_H
macinputcontext.mm
#include "macinputcontext.h"
#include <QDebug>
#include <Cocoa/Cocoa.h>
#include <Carbon/Carbon.h>
MacInputContext::MacInputContext(QObject *parent) : QObject(parent)
{
}
void MacInputContext::updateTextInputContext(bool on)
{
NSTextInputContext * context = [NSTextInputContext currentInputContext];
if(!context)
return ;
if(on)
{
EnableSecureEventInput();
static NSArray *sources = [[NSArray alloc] initWithObjects:&NSAllRomanInputSourcesLocaleIdentifier count:1];
[context setAllowedInputSourceLocales:sources];
}
else
{
DisableSecureEventInput();
[context setAllowedInputSourceLocales:nil];
}
}
macinputcontext.mm는 objective-c c++이며 updateTextInputContext 함수를 보면 objective-c에서 IME를 1byte문자만 허용하게끔 하는 코드입니다.
이제 이 mm파일과 설정에 필요한 cocoa,carbon framework를 사용할 수 있게 .pro에 설정하겠습니다.
macInputContext.pro
OBJECTIVE_SOURCES += \
macinputcontext.mm \
OBJECTIVE_HEADERS += \
macinputcontext.h \
LIBS += -framework Cocoa -framework Carbon
LIBS에서 cocoa, carbon framework를 추가하고
사용하려는 objectie-c를 이용한 .mm파일과 .h파일은 OBJECTIVE_SOURCES OBJECTIVE_HEADERS에 추가해줍니다.
그럼 아래 영상처럼 password filed에 맞게 IME를 설정해줍니다.
