diff --git a/.clang-format b/.clang-format index d743d63..39327cd 100644 --- a/.clang-format +++ b/.clang-format @@ -1,195 +1,107 @@ ---- -# 语言: None, Cpp, Java, JavaScript, ObjC, Proto, TableGen, TextProto +BasedOnStyle: LLVM +IndentWidth: 4 +ColumnLimit: 120 + Language: Cpp -# BasedOnStyle: LLVM -# 访问说明符(public、private等)的偏移 -AccessModifierOffset: -2 -# 开括号(开圆括号、开尖括号、开方括号)后的对齐: Align, DontAlign, AlwaysBreak(总是在开括号后换行) -AlignAfterOpenBracket: Align -# 连续赋值时,对齐所有等号 -AlignConsecutiveAssignments: true -# 连续声明时,对齐所有声明的变量名 -AlignConsecutiveDeclarations: true - -AlignEscapedNewlines: Right - -# 左对齐逃脱换行(使用反斜杠换行)的反斜杠 -#AlignEscapedNewlinesLeft: true -# 水平对齐二元和三元表达式的操作数 -AlignOperands: true -# 对齐连续的尾随的注释 -AlignTrailingComments: true - -# 允许函数声明的所有参数在放在下一行 -AllowAllParametersOfDeclarationOnNextLine: false -# 允许短的块放在同一行 -AllowShortBlocksOnASingleLine: true -# 允许短的case标签放在同一行 -AllowShortCaseLabelsOnASingleLine: true -# 允许短的函数放在同一行: None, InlineOnly(定义在类中), Empty(空函数), Inline(定义在类中,空函数), All -AllowShortFunctionsOnASingleLine: Empty -# 允许短的if语句保持在同一行 -AllowShortIfStatementsOnASingleLine: true -# 允许短的循环保持在同一行 -AllowShortLoopsOnASingleLine: true - -# 总是在定义返回类型后换行(deprecated) -AlwaysBreakAfterDefinitionReturnType: None -# 总是在返回类型后换行: None, All, TopLevel(顶级函数,不包括在类中的函数), -# AllDefinitions(所有的定义,不包括声明), TopLevelDefinitions(所有的顶级函数的定义) -AlwaysBreakAfterReturnType: None -# 总是在多行string字面量前换行 -AlwaysBreakBeforeMultilineStrings: false -# 总是在template声明后换行 -AlwaysBreakTemplateDeclarations: true -# false表示函数实参要么都在同一行,要么都各自一行 -BinPackArguments: false -# false表示所有形参要么都在同一行,要么都各自一行 -BinPackParameters: false -# 大括号换行,只有当BreakBeforeBraces设置为Custom时才有效 +# Strings to be interpreted as attributes/qualifers +AttributeMacros: [] + +# Macros +IndentPPDirectives: BeforeHash +PPIndentWidth: 2 + +# Macros to be interpreted as foreach loops +ForEachMacros: [] + +# Macros to be interpred as conditionals +IfMacros: [] + +# Include +IncludeBlocks: Preserve +SortIncludes: CaseInsensitive + +# Using namespaces +SortUsingDeclarations: true + +# Brace +BreakBeforeBraces: Custom BraceWrapping: - # class定义后面 - AfterClass: false - # 控制语句后面 - AfterControlStatement: false - # enum定义后面 - AfterEnum: false - # 函数定义后面 + AfterCaseLabel: true + AfterClass: true + AfterEnum: true AfterFunction: true - # 命名空间定义后面 - AfterNamespace: false - # ObjC定义后面 - AfterObjCDeclaration: false - # struct定义后面 + AfterNamespace: true AfterStruct: true - # union定义后面 AfterUnion: true - - AfterExternBlock: false - # catch之前 + AfterExternBlock: true BeforeCatch: true - # else之前 BeforeElse: true - # 缩进大括号 - IndentBraces: false + BeforeLambdaBody: true + BeforeWhile: true SplitEmptyFunction: true - SplitEmptyRecord: true - SplitEmptyNamespace: true - -# 在二元运算符前换行: None(在操作符后换行), NonAssignment(在非赋值的操作符前换行), All(在操作符前换行) -BreakBeforeBinaryOperators: None -# 在大括号前换行: Attach(始终将大括号附加到周围的上下文), Linux(除函数、命名空间和类定义,与Attach类似), -# Mozilla(除枚举、函数、记录定义,与Attach类似), Stroustrup(除函数定义、catch、else,与Attach类似), -# Allman(总是在大括号前换行), GNU(总是在大括号前换行,并对于控制语句的大括号增加额外的缩进), WebKit(在函数前换行), Custom -# 注:这里认为语句块也属于函数 -BreakBeforeBraces: Custom -# 在三元运算符前换行 -BreakBeforeTernaryOperators: false - -# 在构造函数的初始化列表的逗号前换行 -BreakConstructorInitializersBeforeComma: false -BreakConstructorInitializers: BeforeColon -# 每行字符的限制,0表示没有限制 -ColumnLimit: 120 -# 描述具有特殊意义的注释的正则表达式,它不应该被分割为多行或以其它方式改变 -CommentPragmas: "^ IWYU pragma:" -CompactNamespaces: false -# 构造函数的初始化列表要么都在同一行,要么都各自一行 -ConstructorInitializerAllOnOneLineOrOnePerLine: false -# 构造函数的初始化列表的缩进宽度 -ConstructorInitializerIndentWidth: 4 -# 延续的行的缩进宽度 -ContinuationIndentWidth: 4 -# 去除C++11的列表初始化的大括号{后和}前的空格 -Cpp11BracedListStyle: true -# 继承最常用的指针和引用的对齐方式 -DerivePointerAlignment: false -# 关闭格式化 -DisableFormat: false -# 自动检测函数的调用和定义是否被格式为每行一个参数(Experimental) -ExperimentalAutoDetectBinPacking: false -# 需要被解读为foreach循环而不是函数调用的宏 -ForEachMacros: [foreach, Q_FOREACH, BOOST_FOREACH] -# 对#include进行排序,匹配了某正则表达式的#include拥有对应的优先级,匹配不到的则默认优先级为INT_MAX(优先级越小排序越靠前), -# 可以定义负数优先级从而保证某些#include永远在最前面 -IncludeCategories: - - Regex: '^"(llvm|llvm-c|clang|clang-c)/' - Priority: 2 - - Regex: '^(<|"(gtest|isl|json)/)' - Priority: 3 - - Regex: ".*" - Priority: 1 -# 缩进case标签 -IndentCaseLabels: true - -IndentPPDirectives: AfterHash -# 缩进宽度 -IndentWidth: 4 -# 函数返回类型换行时,缩进函数声明或函数定义的函数名 -IndentWrappedFunctionNames: false -# 保留在块开始处的空行 + AfterControlStatement: Always KeepEmptyLinesAtTheStartOfBlocks: false -# 开始一个块的宏的正则表达式 -MacroBlockBegin: "" -# 结束一个块的宏的正则表达式 -MacroBlockEnd: "" -# 连续空行的最大数量 -MaxEmptyLinesToKeep: 1 -# 命名空间的缩进: None, Inner(缩进嵌套的命名空间中的内容), All -NamespaceIndentation: All -# 使用ObjC块时缩进宽度 -ObjCBlockIndentWidth: 4 -# 在ObjC的@property后添加一个空格 -ObjCSpaceAfterProperty: false -# 在ObjC的protocol列表前添加一个空格 -ObjCSpaceBeforeProtocolList: true - -# 在call(后对函数调用换行的penalty -PenaltyBreakBeforeFirstCallParameter: 19 -# 在一个注释中引入换行的penalty -PenaltyBreakComment: 300 -# 第一次在<<前换行的penalty -PenaltyBreakFirstLessLess: 120 -# 在一个字符串字面量中引入换行的penalty -PenaltyBreakString: 1000 -# 对于每个在行字符数限制之外的字符的penalty -PenaltyExcessCharacter: 1000000 -# 将函数的返回类型放到它自己的行的penalty -PenaltyReturnTypeOnItsOwnLine: 60 - -# 指针和引用的对齐: Left, Right, Middle -PointerAlignment: Left -# 允许重新排版注释 +SeparateDefinitionBlocks: Always + +# Comment ReflowComments: true -# 允许排序#include -SortIncludes: true - -# 在C风格类型转换后添加空格 -SpaceAfterCStyleCast: false - -SpaceAfterTemplateKeyword: true - -# 在赋值运算符之前添加空格 -SpaceBeforeAssignmentOperators: true -# 开圆括号之前添加一个空格: Never, ControlStatements, Always -SpaceBeforeParens: ControlStatements -# 在空的圆括号中添加空格 -SpaceInEmptyParentheses: false -# 在尾随的评论前添加的空格数(只适用于//) -SpacesBeforeTrailingComments: 0 -# 在尖括号的<后和>前添加空格 -SpacesInAngles: false -# 在容器(ObjC和JavaScript的数组和字典等)字面量中添加空格 -SpacesInContainerLiterals: false -# 在C风格类型转换的括号中添加空格 -SpacesInCStyleCastParentheses: false -# 在圆括号的(后和)前添加空格 -SpacesInParentheses: false -# 在方括号的[后和]前添加空格,lambda表达式和未指明大小的数组的声明不受影响 -SpacesInSquareBrackets: false -# 标准: Cpp03, Cpp11, Auto -Standard: Cpp11 -# tab宽度 -TabWidth: 4 -# 使用tab字符: Never, ForIndentation, ForContinuationAndIndentation, Always -UseTab: Never + +# Class +EmptyLineBeforeAccessModifier: LogicalBlock +EmptyLineAfterAccessModifier: Never +BreakInheritanceList: BeforeComma +BreakConstructorInitializers: BeforeComma +PackConstructorInitializers: Never + +# Alignment +PointerAlignment: Left +ReferenceAlignment: Left +QualifierAlignment: Left + +# Array Structure +AlignArrayOfStructures: Right + +# Consecutive +AlignConsecutiveAssignments: AcrossComments +AlignConsecutiveBitFields: AcrossComments +AlignConsecutiveDeclarations: AcrossComments +AlignConsecutiveMacros: AcrossComments + +# Operands +AlignOperands: AlignAfterOperator +BreakBeforeBinaryOperators: NonAssignment + +# Bit fields +BitFieldColonSpacing: After + +# Function +AllowAllArgumentsOnNextLine: false +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortFunctionsOnASingleLine: None +AlwaysBreakAfterReturnType: None +BinPackArguments: false +BinPackParameters: false + +# Loop +AllowShortLoopsOnASingleLine: false +AllowShortBlocksOnASingleLine: Empty + +# String +AlwaysBreakBeforeMultilineStrings: true +BreakStringLiterals: false + +# If Statement +AllowShortIfStatementsOnASingleLine: WithoutElse + +# Switch Case +AllowShortCaseLabelsOnASingleLine: false + +# Lambda +AllowShortLambdasOnASingleLine: All + +# Enum +AllowShortEnumsOnASingleLine: false + +# Template +AlwaysBreakTemplateDeclarations: Yes +BreakBeforeConceptDeclarations: true diff --git a/.gitmodules b/.gitmodules index bc17d79..dea4209 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,6 @@ [submodule "IngameIME-Common"] path = IngameIME-Common - url = https://github.com/Windmill-City/IngameIME-Common.git + url = git@github.com:Windmill-City/IngameIME-Common.git [submodule "testWnd/glfw"] path = testWnd/glfw url = https://github.com/glfw/glfw diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json deleted file mode 100644 index 23c8941..0000000 --- a/.vscode/c_cpp_properties.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "configurations": [ - { - "name": "Win32", - "includePath": [ - "${workspaceFolder}/**" - ], - "defines": [ - "_DEBUG", - "UNICODE", - "_UNICODE" - ], - "compilerPath": "E:/VisualStudio/2019/Community/VC/Tools/MSVC/14.29.30037/bin/Hostx64/x64/cl.exe", - "cStandard": "c17", - "cppStandard": "c++17", - "intelliSenseMode": "windows-msvc-x64", - "configurationProvider": "vector-of-bool.cmake-tools" - } - ], - "version": 4 -} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 4a3f4f0..2c2fd1a 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,11 +1,98 @@ { "cSpell.words": [ + "AFXFIRST", + "AFXLAST", + "ASKCBFORMATNAME", + "CHANGECBCHAIN", + "CONVERTREQUESTEX", "CPACK", + "CTLCOLORDLG", + "ERASEBKGND", + "GETDLGCODE", + "GETIMESTATUS", + "GLOBALRCCHANGE", + "GWLP", + "HEDITCTL", + "HIMC", + "HOOKRCRESULT", + "HSCROLL", + "HSCROLLCLIPBOARD", + "IACE", + "ICONERASEBKGND", + "IMEKEYDOWN", + "IMEKEYUP", "Ingame", + "LBUTTONDBLCLK", + "LBUTTONDOWN", + "LBUTTONUP", "libtf", + "lparam", + "LPARAM", + "LPPOINT", + "LRESULT", + "MBUTTONDBLCLK", + "MBUTTONDOWN", + "MBUTTONUP", + "MDIACTIVATE", + "MDICASCADE", + "MDICREATE", + "MDIDESTROY", + "MDIGETACTIVE", + "MDIICONARRANGE", + "MDIMAXIMIZE", + "MDINEXT", + "MDIREFRESHMENU", + "MDIRESTORE", + "MDISETMENU", + "MDITILE", + "MENURBUTTONUP", + "msctf", + "NCACTIVATE", + "NCCALCSIZE", + "NCCREATE", + "NCDESTROY", + "NCHITTEST", + "NCLBUTTONDBLCLK", + "NCLBUTTONDOWN", + "NCLBUTTONUP", + "NCMBUTTONUP", + "NCMOUSEHOVER", + "NCMOUSELEAVE", + "NCMOUSEMOVE", + "NCPAINT", + "NCRBUTTONUP", + "NCXBUTTONDBLCLK", + "NCXBUTTONDOWN", + "NCXBUTTONUP", + "NEXTDLGCTL", + "NOINTERFACE", + "PENCTL", + "ppipr", + "pptim", + "QUERYUISTATE", + "RBUTTONDBLCLK", + "RBUTTONDOWN", + "RBUTTONUP", + "RCRESULT", + "REFCLSID", + "REFIID", + "riid", + "SETIMESTATUS", + "SETRECTNP", + "shlwapi", + "SHOWUICANDIDATEWINDOW", + "SHOWUICOMPOSITIONWINDOW", + "TCARD", "UIELEMENTID", "Unadvise", - "WINDOWSSDK" + "UPDATEUISTATE", + "VKEYTOITEM", + "VSCROLL", + "VSCROLLCLIPBOARD", + "WINDOWSSDK", + "XBUTTONDBLCLK", + "XBUTTONDOWN", + "XBUTTONUP" ], "files.associations": { "stop_token": "cpp", @@ -84,5 +171,6 @@ "mutex": "cpp", "numeric": "cpp", "*.rh": "cpp" - } + }, + "python.formatting.provider": "autopep8", } \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 645af29..bb9ac79 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -35,12 +35,9 @@ message(STATUS "Build version: ${VERSION}") # Build files ########################### -set(include_dir - ${CMAKE_CURRENT_SOURCE_DIR}/src/include - ${CMAKE_CURRENT_SOURCE_DIR}/src/include/common - ${CMAKE_CURRENT_SOURCE_DIR}/src/include/tf - ${CMAKE_CURRENT_SOURCE_DIR}/src/include/imm) -set(source_file ${CMAKE_CURRENT_SOURCE_DIR}/src/IngameIMEImpl.cpp ${version_file}) +set(include_dir ${CMAKE_CURRENT_SOURCE_DIR}/include) +file(GLOB_RECURSE source_file ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp) +list(APPEND source_file ${version_file}) ########################### # Targets diff --git a/IngameIME-Common b/IngameIME-Common index 446b5da..8eff3c6 160000 --- a/IngameIME-Common +++ b/IngameIME-Common @@ -1 +1 @@ -Subproject commit 446b5dacc7710337869c6b414f63ed249f417b05 +Subproject commit 8eff3c608375f6022bd582f91c688c60ffbdf379 diff --git a/include/DebugMsg.h b/include/DebugMsg.h new file mode 100644 index 0000000..f483556 --- /dev/null +++ b/include/DebugMsg.h @@ -0,0 +1,10 @@ +#pragma once +#include + +#include + +std::wstring GetMessageText(UINT msg) noexcept; + +void ShowUsedMsg() noexcept; + +void ShowMessageText(UINT msg) noexcept; \ No newline at end of file diff --git a/include/FormatUtil.hpp b/include/FormatUtil.hpp new file mode 100644 index 0000000..a788077 --- /dev/null +++ b/include/FormatUtil.hpp @@ -0,0 +1,11 @@ +#pragma once +#include + +#include + +namespace IngameIME +{ +std::string format(const HRESULT hr); +std::string format(const char* pMessage, ...); +std::wstring format(const wchar_t* pMessage, ...); +} // namespace IngameIME \ No newline at end of file diff --git a/include/Singleton.hpp b/include/Singleton.hpp new file mode 100644 index 0000000..a06b2a6 --- /dev/null +++ b/include/Singleton.hpp @@ -0,0 +1,52 @@ +#pragma once +#include +#include +#include + +namespace IngameIME +{ +template +class Singleton +{ + protected: + using RefHolderType = std::map>; + + protected: + static std::mutex RefHolderMutex; + static RefHolderType WeakRefs; + + private: + const Arg arg; + + public: + static std::shared_ptr getOrCreate(Arg arg) + { + std::lock_guard guard(RefHolderMutex); + auto iter = WeakRefs.find(arg); + + std::shared_ptr result; + if (iter == WeakRefs.end() || !(result = iter->second.lock())) + { + result = std::make_shared(arg); + WeakRefs[arg] = result; + } + return result; + } + + public: + Singleton(const Singleton&) = delete; + Singleton(Singleton&&) = delete; + + protected: + Singleton(const Arg arg) + : arg(arg) + { + } + + public: + ~Singleton() + { + WeakRefs.erase(arg); + } +}; +} // namespace IngameIME diff --git a/include/common/InputContextImpl.hpp b/include/common/InputContextImpl.hpp new file mode 100644 index 0000000..8b870b3 --- /dev/null +++ b/include/common/InputContextImpl.hpp @@ -0,0 +1,19 @@ +#pragma once +#include + +#include "InputContext.hpp" + +namespace IngameIME +{ +struct InternalRect : public PreEditRect +{ + InternalRect() = default; + + /** + * @brief Implicit convert to RECT + * + * @return RECT + */ + operator RECT() noexcept; +}; +} // namespace IngameIME \ No newline at end of file diff --git a/include/common/InputProcessorImpl.hpp b/include/common/InputProcessorImpl.hpp new file mode 100644 index 0000000..a328529 --- /dev/null +++ b/include/common/InputProcessorImpl.hpp @@ -0,0 +1,85 @@ +#pragma once +#include +#include +#include + +#include +#pragma comment(lib, "imm32.lib") + +#include +#pragma comment(lib, "shlwapi.lib") + +#include + +#include "InputProcessor.hpp" + +#include "FormatUtil.hpp" +#include "Singleton.hpp" +#include "tf\ComBSTR.hpp" +#include "tf\ComObjectBase.hpp" +#include "tf\ComPtr.hpp" +#include "tf\TfFunction.hpp" + +namespace IngameIME +{ +class InternalLocale + : public Locale + , public Singleton +{ + protected: + const LANGID langId; + + protected: + static std::wstring getLocaleString(const LANGID langid); + static std::wstring getLocaleName(const LANGID langid); + + public: + InternalLocale(const LANGID langId) noexcept; +}; + +struct ComparableProfile : TF_INPUTPROCESSORPROFILE +{ + ComparableProfile(const TF_INPUTPROCESSORPROFILE&); + bool operator<(const ComparableProfile& s2) const; +}; + +class InputProcessorImpl + : public InputProcessor + , public Singleton +{ + protected: + /** + * @brief Check if the HKL correspond to a Imm InputMethod + * + * @param hkl hkl to check + * @return true - Imm InputMethod + * @return false - Normal KeyboardLayout + */ + static bool isImm(HKL hkl); + + static std::wstring getInputProcessorName(const TF_INPUTPROCESSORPROFILE& profile); + static std::wstring getKeyboardLayoutName(LANGID langId); + static std::wstring getTextServiceName(const TF_INPUTPROCESSORPROFILE& profile); + static std::wstring getImmName(HKL hkl); + + protected: + const TF_INPUTPROCESSORPROFILE profile; + + public: + static std::shared_ptr getActiveInputProcessor(); + static std::list> getInputProcessors(); + + public: + /** + * @brief If this is a Japanese InputProcessor + * + */ + bool isJap; + + public: + InputProcessorImpl(const TF_INPUTPROCESSORPROFILE& profile); + + public: + virtual void setActivated() const override; +}; +} // namespace IngameIME \ No newline at end of file diff --git a/include/imm/ImmCompositionImpl.hpp b/include/imm/ImmCompositionImpl.hpp new file mode 100644 index 0000000..6793699 --- /dev/null +++ b/include/imm/ImmCompositionImpl.hpp @@ -0,0 +1,21 @@ +#pragma once +#include "ImmInputContextImpl.hpp" + +namespace IngameIME::imm +{ +class CompositionImpl : public Composition +{ + protected: + InputContextImpl* inputCtx; + + public: + CompositionImpl(InputContextImpl* inputCtx); + + public: + /** + * @brief Terminate active composition + * + */ + virtual void terminate() noexcept override; +}; +} // namespace IngameIME::imm diff --git a/include/imm/ImmIngameIMEImpl.hpp b/include/imm/ImmIngameIMEImpl.hpp new file mode 100644 index 0000000..8a54dd7 --- /dev/null +++ b/include/imm/ImmIngameIMEImpl.hpp @@ -0,0 +1,31 @@ +#pragma once +#include "common/InputProcessorImpl.hpp" +#include "IngameIME.hpp" + +#include "ImmInputContextImpl.hpp" + +namespace IngameIME::imm +{ +class GlobalImpl : public Global +{ + public: + /** + * @brief Get Active InputProcessor + * + * @return std::shared_ptr + */ + virtual std::shared_ptr getActiveInputProcessor() const override; + /** + * @brief Get system availiable InputProcessor + * + * @return std::list> + */ + virtual std::list> getInputProcessors() const override; + /** + * @brief Get the InputContext object + * + * @return std::shared_ptr + */ + virtual std::shared_ptr getInputContext(void* hWnd, ...) override; +}; +} // namespace IngameIME::imm \ No newline at end of file diff --git a/include/imm/ImmInputContextImpl.hpp b/include/imm/ImmInputContextImpl.hpp new file mode 100644 index 0000000..554905c --- /dev/null +++ b/include/imm/ImmInputContextImpl.hpp @@ -0,0 +1,87 @@ +#pragma once +#include + +#include +#pragma comment(lib, "imm32.lib") + +#include "IngameIME.hpp" + +#include "common/InputContextImpl.hpp" +#include "common/InputProcessorImpl.hpp" +#include "Singleton.hpp" + +namespace IngameIME::imm +{ +class InputContextImpl + : public InputContext + , public Singleton +{ + protected: + static LRESULT WndProc(HWND hWnd, UINT msg, WPARAM wparam, LPARAM lparam); + + protected: + HWND hWnd; + WNDPROC prevProc; + + HIMC ctx; + + bool activated{false}; + bool fullscreen{false}; + + friend class CompositionImpl; + friend class GlobalImpl; + + public: + InputContextImpl(const HWND hWnd); + ~InputContextImpl(); + + protected: + /** + * @brief Retrive PreEdit info for current Composition + */ + void procPreEdit(); + /** + * @brief Retrive Commit text for current Composition + */ + void procCommit(); + /** + * @brief Set CandidateList window's position for current Composition + */ + void procPreEditRect(); + /** + * @brief Retrive CandidateList infomation for application to draw + */ + void procCand(); + + public: + InputProcessorContext getInputProcCtx(); + + public: + /** + * @brief Set InputContext activate state + * + * @param activated if InputContext activated + */ + virtual void setActivated(const bool activated) noexcept override; + /** + * @brief Get if InputContext activated + * + * @return true activated + * @return false not activated + */ + virtual bool getActivated() const noexcept override; + /** + * @brief Set InputContext full screen state + * + * @param fullscreen if InputContext full screen + */ + virtual void setFullScreen(const bool fullscreen) noexcept override; + /** + * @brief Get if InputContext in full screen state + * + * @return true full screen mode + * @return false window mode + */ + virtual bool getFullScreen() const noexcept override; +}; +} // namespace IngameIME::imm \ No newline at end of file diff --git a/include/tf/ComBSTR.hpp b/include/tf/ComBSTR.hpp new file mode 100644 index 0000000..cd81cbb --- /dev/null +++ b/include/tf/ComBSTR.hpp @@ -0,0 +1,39 @@ +#pragma once +#include + +#include + +namespace IngameIME::tf +{ +class ComBSTR +{ + public: + BSTR bstr{nullptr}; + + public: + constexpr ComBSTR() = default; + + constexpr ComBSTR(std::nullptr_t) noexcept + { + } + + ~ComBSTR(); + + /** + * @brief Acquire address of bstr + * + * this usually use for acquiring BSTR string, so the bstr must be null, + * if you do want to acquire the address of the bstr, use &bstr instead + * + * @return address of the bstr + */ + [[nodiscard]] BSTR* operator&(); + /** + * @brief Check if the BSTR is nullptr + * + * @return true + * @return false + */ + explicit operator bool() const noexcept; +}; +} // namespace IngameIME::tf \ No newline at end of file diff --git a/include/tf/ComException.hpp b/include/tf/ComException.hpp new file mode 100644 index 0000000..6c08623 --- /dev/null +++ b/include/tf/ComException.hpp @@ -0,0 +1,16 @@ +#pragma once +#include + +#include "FormatUtil.hpp" + +namespace IngameIME::tf +{ +class ComException : public std::runtime_error +{ + public: + const HRESULT hr; + + public: + ComException(HRESULT hr); +}; +} // namespace IngameIME::tf diff --git a/include/tf/ComObjectBase.hpp b/include/tf/ComObjectBase.hpp new file mode 100644 index 0000000..6575958 --- /dev/null +++ b/include/tf/ComObjectBase.hpp @@ -0,0 +1,66 @@ +#pragma once +#include + +#include "ComException.hpp" + +#define COM_DEF_BEGIN() \ + virtual HRESULT STDMETHODCALLTYPE QueryInterface( \ + /* [in] */ REFIID riid, \ + /* [iid_is][out] */ _COM_Outptr_ void __RPC_FAR* __RPC_FAR* ppvObject) override \ + { \ + if (!ppvObject) return E_POINTER; \ + *ppvObject = nullptr; \ + if (IsEqualIID(IID_IUnknown, riid)) *ppvObject = this; +#define COM_DEF_INF(InfName) \ + if (IsEqualIID(IID_##InfName, riid)) *ppvObject = static_cast(this); +#define COM_DEF_END() \ + if (*ppvObject) \ + { \ + this->AddRef(); \ + return S_OK; \ + } \ + return E_NOINTERFACE; \ + } \ + virtual ULONG STDMETHODCALLTYPE AddRef(void) override \ + { \ + return ComObjectBase::InternalAddRef(); \ + } \ + virtual ULONG STDMETHODCALLTYPE Release(void) override \ + { \ + return ComObjectBase::InternalRelease(); \ + } + +#define COM_HR_BEGIN(DefaultHR) \ + HRESULT hr = DefaultHR; \ + do \ + { +#define COM_HR_END() \ + } \ + while (0) \ + ; +#define THR_HR(hr) throw IngameIME::tf::ComException(hr); +#define CHECK_HR(exp) \ + if (FAILED(hr = (exp))) break; +#define COM_HR_THR() \ + if (FAILED(hr)) THR_HR(hr); +#define COM_HR_RET() return hr; + +namespace IngameIME::tf +{ +class ComObjectBase +{ + private: + /** + * @brief Reference count of the ComObj + * + */ + DWORD ref = 0; + + public: + virtual ~ComObjectBase() = default; + + protected: + ULONG InternalAddRef(void); + ULONG InternalRelease(void); +}; +} // namespace IngameIME::tf \ No newline at end of file diff --git a/include/tf/ComPtr.hpp b/include/tf/ComPtr.hpp new file mode 100644 index 0000000..cc66a36 --- /dev/null +++ b/include/tf/ComPtr.hpp @@ -0,0 +1,167 @@ +#pragma once +#include + +namespace IngameIME::tf +{ +template +class ComPtr +{ + protected: + T* ptr{nullptr}; + + public: + constexpr ComPtr() = default; + + constexpr ComPtr(std::nullptr_t) noexcept + { + } + + explicit ComPtr(T* ptr) noexcept + : ptr(ptr) + { + if (ptr) ptr->AddRef(); + } + + ComPtr(const ComPtr& other) noexcept + { + ptr = other.ptr; + if (ptr) ptr->AddRef(); + } + + ComPtr(const ComPtr&& other) noexcept + { + ptr = other.ptr; + other.ptr = nullptr; + } + + ~ComPtr() noexcept + { + if (ptr) ptr->Release(); + } + + public: + [[nodiscard]] T* get() const noexcept + { + return ptr; + } + + void swap(ComPtr& other) noexcept + { + std::swap(ptr, other.ptr); + } + + void reset() noexcept + { + ComPtr().swap(*this); + } + + void reset(T* ptr) noexcept + { + ComPtr(ptr).swap(*this); + } + + /** + * @brief Attach to excting interface without AddRef() + * + * @param ptr pointer to interface + */ + void attach(T* ptr) noexcept + { + reset(); + this->ptr = ptr; + } + + /** + * @brief Detach the interface without Release() + * + * @return pointer to the detached interface + */ + T* detach() noexcept + { + T* p = ptr; + ptr = nullptr; + return p; + } + + ComPtr& operator=(const ComPtr& right) noexcept + { + ComPtr(right).swap(*this); + return *this; + } + + ComPtr& operator=(T* ptr) noexcept + { + ComPtr(ptr).swap(*this); + return *this; + } + + [[nodiscard]] T& operator*() const noexcept + { + return *ptr; + } + + [[nodiscard]] T* operator->() const noexcept + { + return ptr; + } + + /** + * @brief Acquire address of raw pointer + * + * this usually use for acquiring the interface, so the pointer must be null, + * if you do want to acquire the address of the raw pointer, use &get() instead + * + * @return address of the raw pointer + */ + [[nodiscard]] T** operator&() + { + if (ptr) throw new std::runtime_error("Acquire address for non-null pointer"); + return &ptr; + } + + explicit operator bool() const noexcept + { + return ptr != nullptr; + } +}; + +template +class ComQIPtr : public ComPtr +{ + protected: + const IID iid; + + public: + constexpr ComQIPtr(const IID iid) noexcept + : iid(iid) + { + } + + constexpr ComQIPtr(const IID iid, std::nullptr_t) noexcept + : iid(iid) + { + } + + template + ComQIPtr(const IID iid, const ComPtr& other) noexcept + : iid(iid) + { + IUnknown* p; + if (!other || FAILED(other->QueryInterface(iid, (void**)&p))) + { + this->ptr = nullptr; + } + else + { + this->ptr = reinterpret_cast(p); + } + } + + template + ComQIPtr& operator=(const ComPtr& other) noexcept + { + ComQIPtr(iid, other).swap(*this); + return *this; + } +}; +} // namespace IngameIME::tf \ No newline at end of file diff --git a/include/tf/IThreadAssociate.hpp b/include/tf/IThreadAssociate.hpp new file mode 100644 index 0000000..6e41cb9 --- /dev/null +++ b/include/tf/IThreadAssociate.hpp @@ -0,0 +1,35 @@ +#pragma once +#include +#include + +namespace IngameIME::tf +{ +class IThreadAssociate +{ + private: + DWORD threadId; + + protected: + /** + * @brief Initialize the creator thread + * + * @param threadId creator thread id, set to null to use current thread + * @return DWORD creator thread id + */ + DWORD initialCreatorThread(const DWORD threadId = 0); + + public: + /** + * @brief Assert the calling thread is the thread that create this object + * + * @return UI_E_WRONG_THREAD if the calling thread isn't the thread that create the object + */ + HRESULT assertCreatorThread() const; + /** + * @brief Get the Creator Thread Id + * + * @return DWORD creator thread id + */ + DWORD getCreatorThreadId() const; +}; +} // namespace IngameIME::tf diff --git a/include/tf/TfCompositionImpl.hpp b/include/tf/TfCompositionImpl.hpp new file mode 100644 index 0000000..205986b --- /dev/null +++ b/include/tf/TfCompositionImpl.hpp @@ -0,0 +1,85 @@ +#pragma once +#include + +#include "ComBSTR.hpp" +#include "TfInputContextImpl.hpp" + +namespace IngameIME::tf +{ +class CompositionImpl : public Composition +{ + private: + DWORD cookieEditSink{TF_INVALID_COOKIE}; + DWORD cookieEleSink{TF_INVALID_COOKIE}; + + protected: + class CompositionHandler; + InputContextImpl* inputCtx; + ComPtr handler; + + protected: + class CompositionHandler + : protected ComObjectBase + , public ITfContextOwnerCompositionSink + , public ITfTextEditSink + , public ITfEditSession + , public ITfUIElementSink + { + protected: + CompositionImpl* comp; + + ComPtr compView; + + ComQIPtr eleMgr{IID_ITfUIElementMgr}; + ComPtr ele; + DWORD eleId{TF_INVALID_UIELEMENTID}; + + public: + CompositionHandler(CompositionImpl* comp); + + public: + COM_DEF_BEGIN(); + COM_DEF_INF(ITfContextOwnerCompositionSink); + COM_DEF_INF(ITfTextEditSink); + COM_DEF_INF(ITfEditSession); + COM_DEF_INF(ITfUIElementSink); + COM_DEF_END(); + + public: + HRESULT STDMETHODCALLTYPE OnStartComposition(ITfCompositionView* pComposition, BOOL* pfOk) override; + HRESULT STDMETHODCALLTYPE OnUpdateComposition(ITfCompositionView* pComposition, ITfRange* pRangeNew) override; + HRESULT STDMETHODCALLTYPE OnEndComposition(ITfCompositionView* pComposition) override; + + public: + /** + * @brief Get PreEdit text and its selection + * + * @note Selection change only triggers OnEndEdit event, + * for convenient, we handle preedit here at the same time + */ + HRESULT STDMETHODCALLTYPE OnEndEdit(ITfContext* pic, TfEditCookie ec, ITfEditRecord* pEditRecord) override; + /** + * @brief Get all the text in the context, which is commit string + */ + HRESULT STDMETHODCALLTYPE DoEditSession(TfEditCookie ec) override; + + public: + /** + * @brief Hide all the window of the input method if in full screen mode + */ + HRESULT STDMETHODCALLTYPE BeginUIElement(DWORD dwUIElementId, BOOL* pbShow) override; + HRESULT STDMETHODCALLTYPE UpdateUIElement(DWORD dwUIElementId) override; + HRESULT STDMETHODCALLTYPE EndUIElement(DWORD dwUIElementId) override; + }; + + public: + CompositionImpl(InputContextImpl* inputCtx); + ~CompositionImpl(); + + public: + /** + * @brief Terminate active composition + */ + virtual void terminate() override; +}; +} // namespace IngameIME::tf diff --git a/include/tf/TfFunction.hpp b/include/tf/TfFunction.hpp new file mode 100644 index 0000000..bfd30d2 --- /dev/null +++ b/include/tf/TfFunction.hpp @@ -0,0 +1,31 @@ +#pragma once +#include +#include + +#include +#include + +namespace IngameIME::tf +{ +using pTF_CreateThreadMgr = HRESULT(WINAPI*)(ITfThreadMgr**); +using pTF_CreateInputProcessorProfiles = HRESULT(WINAPI*)(ITfInputProcessorProfiles**); +using pTF_GetThreadMgr = HRESULT(WINAPI*)(ITfThreadMgr**); + +/** + * @brief Create ThreadMgr for the calling thread without initializing COM + * + * ThreadMgr created by COM is using Apartment Model, which makes it problematic in MultiThreaded Model + */ +HRESULT createThreadMgr(ITfThreadMgr** pptim); +/** + * @brief Obtains a copy of ThreadMgr previously created within the calling thread, + * or create new if no previous one + */ +HRESULT getThreadMgr(ITfThreadMgr** pptim); +/** + * @brief Create InputProcessorProfiles for the calling thread without initializing COM + * + * InputProcessorProfiles created by COM is using Apartment Model, which makes it problematic in MultiThreaded Model + */ +HRESULT createInputProcessorProfiles(ITfInputProcessorProfiles** ppipr); +} // namespace IngameIME::tf \ No newline at end of file diff --git a/include/tf/TfIngameIMEImpl.hpp b/include/tf/TfIngameIMEImpl.hpp new file mode 100644 index 0000000..0e95bef --- /dev/null +++ b/include/tf/TfIngameIMEImpl.hpp @@ -0,0 +1,44 @@ +#pragma once +#include "IngameIME.hpp" + +#include "TfInputContextImpl.hpp" +#include "TfInputProcessorImpl.hpp" + +namespace IngameIME::tf +{ +class GlobalImpl : public Global +{ + private: + DWORD cookieComp{TF_INVALID_COOKIE}; + DWORD cookieProc{TF_INVALID_COOKIE}; + + protected: + ComPtr handler; + friend class Global; + + protected: + GlobalImpl(); + ~GlobalImpl(); + + public: + /** + * @brief Get Active InputProcessor + * + * @return std::shared_ptr + */ + virtual std::shared_ptr getActiveInputProcessor() const override; + /** + * @brief Get system availiable InputProcessor + * + * @return std::list> + */ + virtual std::list> getInputProcessors() const override; + /** + * @brief Get the InputContext object + * + * @param hWnd the window to create InputContext + * @return std::shared_ptr + */ + virtual std::shared_ptr getInputContext(void* hWnd, ...) override; +}; +} // namespace IngameIME::tf \ No newline at end of file diff --git a/include/tf/TfInputContextImpl.hpp b/include/tf/TfInputContextImpl.hpp new file mode 100644 index 0000000..8a521c0 --- /dev/null +++ b/include/tf/TfInputContextImpl.hpp @@ -0,0 +1,118 @@ +#pragma once +#include + +#include "common/InputContextImpl.hpp" +#include "InputProcessor.hpp" + +#include + +#include "ComObjectBase.hpp" +#include "ComPtr.hpp" +#include "IThreadAssociate.hpp" +#include "Singleton.hpp" +#include "TfFunction.hpp" + +namespace IngameIME::tf +{ +class InputContextImpl + : public InputContext + , public IThreadAssociate +{ + class ContextOwner; + friend class CompositionImpl; + + private: + const HWND hWnd; + + ComPtr threadMgr; + ComPtr docMgr; + ComPtr emptyDocMgr; + ComPtr ctx; + ComPtr owner; + TfClientId clientId{TF_CLIENTID_NULL}; + DWORD cookie{TF_INVALID_COOKIE}; + + bool activated{false}; + bool fullscreen{false}; + + protected: + class ContextOwner + : protected ComObjectBase + , public ITfContextOwner + { + protected: + InputContextImpl* ctx; + + public: + ContextOwner(InputContextImpl* ctx); + + public: + COM_DEF_BEGIN(); + COM_DEF_INF(ITfContextOwner); + COM_DEF_END(); + + public: + /** + * @brief Our implementation does not calculate a text layout + * + * @return HRESULT TS_E_NOLAYOUT + */ + HRESULT STDMETHODCALLTYPE GetACPFromPoint(const POINT* ptScreen, DWORD dwFlags, LONG* pacp) override; + /** + * @brief Input method call this method to get the bounding box, in screen coordinates, of preedit string, + * and use which to position its candidate window + */ + HRESULT STDMETHODCALLTYPE GetTextExt(LONG acpStart, LONG acpEnd, RECT* prc, BOOL* pfClipped) override; + /** + * @brief Return the bounding box, in screen coordinates, of the display surface of the text stream + */ + HRESULT STDMETHODCALLTYPE GetScreenExt(RECT* prc) override; + /** + * @brief Obtains the status of the context. + */ + HRESULT STDMETHODCALLTYPE GetStatus(TF_STATUS* pdcs) override; + /** + * @brief Return the window handle the InputContext is associated to + * + * Retrive this value by calling ITfContextView::GetWnd, ITfContextView can query from ITfContext + */ + HRESULT STDMETHODCALLTYPE GetWnd(HWND* phwnd) override; + /** + * @brief Our implementation doesn't support any attributes, just return VT_EMPTY + */ + HRESULT STDMETHODCALLTYPE GetAttribute(REFGUID rguidAttribute, VARIANT* pvarValue) override; + }; + + public: + InputContextImpl(const HWND hWnd); + ~InputContextImpl(); + + public: + /** + * @brief Set if context activated + * + * @param activated set to true to activate input method + * @throw UI_E_WRONG_THREAD if the calling thread isn't the thread that create the context + */ + virtual void setActivated(const bool activated) override; + /** + * @brief Get context activate state + * + * @return if the context activated + */ + virtual bool getActivated() const override; + /** + * @brief Set InputContext full screen state + * + * @param fullscreen if InputContext full screen + */ + virtual void setFullScreen(const bool fullscreen) override; + /** + * @brief Get if InputContext in full screen state + * + * @return true full screen mode + * @return false window mode + */ + virtual bool getFullScreen() const override; +}; +} // namespace IngameIME::tf \ No newline at end of file diff --git a/include/tf/TfInputProcessorImpl.hpp b/include/tf/TfInputProcessorImpl.hpp new file mode 100644 index 0000000..8126cc0 --- /dev/null +++ b/include/tf/TfInputProcessorImpl.hpp @@ -0,0 +1,111 @@ +#pragma once +#include "..\common\InputProcessorImpl.hpp" + +namespace IngameIME::tf +{ +class InputProcessorHandler + : public ComObjectBase + , public ITfInputProcessorProfileActivationSink + , public ITfCompartmentEventSink +{ + public: + ComQIPtr compMgr{IID_ITfCompartmentMgr}; + ComPtr mode; + + public: + InputProcessorHandler() + { + COM_HR_BEGIN(S_OK); + + ComPtr threadMgr; + CHECK_HR(getThreadMgr(&threadMgr)); + compMgr = threadMgr; + + CHECK_HR(compMgr->GetCompartment(GUID_COMPARTMENT_KEYBOARD_INPUTMODE_CONVERSION, &mode)); + + COM_HR_END(); + COM_HR_THR(); + } + + protected: + InputProcessorContext getCtx() + { + InputProcessorContext result; + + auto activeProc = InputProcessorImpl::getActiveInputProcessor(); + result.proc = activeProc; + + VARIANT var; + mode->GetValue(&var); + + if (activeProc->type == InputProcessorType::KeyboardLayout) + result.modes.push_back(L"AlphaNumeric"); + else + { + if (var.intVal & TF_CONVERSIONMODE_NATIVE) + { + result.modes.push_back(L"Native"); + + if (activeProc->isJap) + if (var.intVal & TF_CONVERSIONMODE_KATAKANA) + result.modes.push_back(L"Katakana"); + else + result.modes.push_back(L"Hiragana"); + } + else + result.modes.push_back(L"AlphaNumeric"); + + if (var.intVal & TF_CONVERSIONMODE_FULLSHAPE) + result.modes.push_back(L"FullShape"); + else + result.modes.push_back(L"HalfShape"); + } + + return result; + } + + public: + COM_DEF_BEGIN(); + COM_DEF_INF(ITfInputProcessorProfileActivationSink); + COM_DEF_INF(ITfCompartmentEventSink); + COM_DEF_END(); + + HRESULT STDMETHODCALLTYPE OnActivated(DWORD dwProfileType, + LANGID langid, + REFCLSID clsid, + REFGUID catid, + REFGUID guidProfile, + HKL hkl, + DWORD dwFlags) + { + COM_HR_BEGIN(S_OK); + + // Only notify active inputprocessor + if (!(dwFlags & TF_IPSINK_FLAG_ACTIVE)) return S_OK; + + TF_INPUTPROCESSORPROFILE profile; + profile.dwProfileType = dwProfileType; + profile.langid = langid; + profile.clsid = clsid; + profile.catid = catid; + profile.guidProfile = guidProfile; + profile.hkl = hkl; + + Global::getInstance().runCallback(InputProcessorState::FullUpdate, getCtx()); + + COM_HR_END(); + COM_HR_RET(); + } + + HRESULT STDMETHODCALLTYPE OnChange(REFGUID rguid) override + { + COM_HR_BEGIN(S_OK); + + if (IsEqualGUID(rguid, GUID_COMPARTMENT_KEYBOARD_INPUTMODE_CONVERSION)) + Global::getInstance().runCallback(InputProcessorState::InputModeUpdate, getCtx()); + + COM_HR_END(); + COM_HR_RET(); + } +}; +} // namespace IngameIME::tf \ No newline at end of file diff --git a/run-clang-format.py b/run-clang-format.py new file mode 100644 index 0000000..dbe4c4a --- /dev/null +++ b/run-clang-format.py @@ -0,0 +1,408 @@ +#!/usr/bin/env python +"""A wrapper script around clang-format, suitable for linting multiple files +and to use for continuous integration. + +This is an alternative API for the clang-format command line. +It runs over multiple files and directories in parallel. +A diff output is produced and a sensible exit code is returned. + +""" + +from __future__ import print_function, unicode_literals + +import argparse +import codecs +import difflib +import fnmatch +import io +import errno +import multiprocessing +import os +import signal +import subprocess +import sys +import traceback + +from functools import partial + +try: + from subprocess import DEVNULL # py3k +except ImportError: + DEVNULL = open(os.devnull, "wb") + + +DEFAULT_EXTENSIONS = 'c,h,C,H,cpp,hpp,cc,hh,c++,h++,cxx,hxx' +DEFAULT_CLANG_FORMAT_IGNORE = '.clang-format-ignore' + + +class ExitStatus: + SUCCESS = 0 + DIFF = 1 + TROUBLE = 2 + +def excludes_from_file(ignore_file): + excludes = [] + try: + with io.open(ignore_file, 'r', encoding='utf-8') as f: + for line in f: + if line.startswith('#'): + # ignore comments + continue + pattern = line.rstrip() + if not pattern: + # allow empty lines + continue + excludes.append(pattern) + except EnvironmentError as e: + if e.errno != errno.ENOENT: + raise + return excludes; + +def list_files(files, recursive=False, extensions=None, exclude=None): + if extensions is None: + extensions = [] + if exclude is None: + exclude = [] + + out = [] + for file in files: + if recursive and os.path.isdir(file): + for dirpath, dnames, fnames in os.walk(file): + fpaths = [os.path.join(dirpath, fname) for fname in fnames] + for pattern in exclude: + # os.walk() supports trimming down the dnames list + # by modifying it in-place, + # to avoid unnecessary directory listings. + dnames[:] = [ + x for x in dnames + if + not fnmatch.fnmatch(os.path.join(dirpath, x), pattern) + ] + fpaths = [ + x for x in fpaths if not fnmatch.fnmatch(x, pattern) + ] + for f in fpaths: + ext = os.path.splitext(f)[1][1:] + if ext in extensions: + out.append(f) + else: + out.append(file) + return out + + +def make_diff(file, original, reformatted): + return list( + difflib.unified_diff( + original, + reformatted, + fromfile='{}\t(original)'.format(file), + tofile='{}\t(reformatted)'.format(file), + n=3)) + + +class DiffError(Exception): + def __init__(self, message, errs=None): + super(DiffError, self).__init__(message) + self.errs = errs or [] + + +class UnexpectedError(Exception): + def __init__(self, message, exc=None): + super(UnexpectedError, self).__init__(message) + self.formatted_traceback = traceback.format_exc() + self.exc = exc + + +def run_clang_format_diff_wrapper(args, file): + try: + ret = run_clang_format_diff(args, file) + return ret + except DiffError: + raise + except Exception as e: + raise UnexpectedError('{}: {}: {}'.format(file, e.__class__.__name__, + e), e) + + +def run_clang_format_diff(args, file): + try: + with io.open(file, 'r', encoding='utf-8') as f: + original = f.readlines() + except IOError as exc: + raise DiffError(str(exc)) + + if args.in_place: + invocation = [args.clang_format_executable, '-i', file] + else: + invocation = [args.clang_format_executable, file] + + if args.style: + invocation.extend(['--style', args.style]) + + if args.dry_run: + print(" ".join(invocation)) + return [], [] + + # Use of utf-8 to decode the process output. + # + # Hopefully, this is the correct thing to do. + # + # It's done due to the following assumptions (which may be incorrect): + # - clang-format will returns the bytes read from the files as-is, + # without conversion, and it is already assumed that the files use utf-8. + # - if the diagnostics were internationalized, they would use utf-8: + # > Adding Translations to Clang + # > + # > Not possible yet! + # > Diagnostic strings should be written in UTF-8, + # > the client can translate to the relevant code page if needed. + # > Each translation completely replaces the format string + # > for the diagnostic. + # > -- http://clang.llvm.org/docs/InternalsManual.html#internals-diag-translation + # + # It's not pretty, due to Python 2 & 3 compatibility. + encoding_py3 = {} + if sys.version_info[0] >= 3: + encoding_py3['encoding'] = 'utf-8' + + try: + proc = subprocess.Popen( + invocation, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + universal_newlines=True, + **encoding_py3) + except OSError as exc: + raise DiffError( + "Command '{}' failed to start: {}".format( + subprocess.list2cmdline(invocation), exc + ) + ) + proc_stdout = proc.stdout + proc_stderr = proc.stderr + if sys.version_info[0] < 3: + # make the pipes compatible with Python 3, + # reading lines should output unicode + encoding = 'utf-8' + proc_stdout = codecs.getreader(encoding)(proc_stdout) + proc_stderr = codecs.getreader(encoding)(proc_stderr) + # hopefully the stderr pipe won't get full and block the process + outs = list(proc_stdout.readlines()) + errs = list(proc_stderr.readlines()) + proc.wait() + if proc.returncode: + raise DiffError( + "Command '{}' returned non-zero exit status {}".format( + subprocess.list2cmdline(invocation), proc.returncode + ), + errs, + ) + if args.in_place: + return [], errs + return make_diff(file, original, outs), errs + + +def bold_red(s): + return '\x1b[1m\x1b[31m' + s + '\x1b[0m' + + +def colorize(diff_lines): + def bold(s): + return '\x1b[1m' + s + '\x1b[0m' + + def cyan(s): + return '\x1b[36m' + s + '\x1b[0m' + + def green(s): + return '\x1b[32m' + s + '\x1b[0m' + + def red(s): + return '\x1b[31m' + s + '\x1b[0m' + + for line in diff_lines: + if line[:4] in ['--- ', '+++ ']: + yield bold(line) + elif line.startswith('@@ '): + yield cyan(line) + elif line.startswith('+'): + yield green(line) + elif line.startswith('-'): + yield red(line) + else: + yield line + + +def print_diff(diff_lines, use_color): + if use_color: + diff_lines = colorize(diff_lines) + if sys.version_info[0] < 3: + sys.stdout.writelines((l.encode('utf-8') for l in diff_lines)) + else: + sys.stdout.writelines(diff_lines) + + +def print_trouble(prog, message, use_colors): + error_text = 'error:' + if use_colors: + error_text = bold_red(error_text) + print("{}: {} {}".format(prog, error_text, message), file=sys.stderr) + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument( + '--clang-format-executable', + metavar='EXECUTABLE', + help='path to the clang-format executable', + default='clang-format') + parser.add_argument( + '--extensions', + help='comma separated list of file extensions (default: {})'.format( + DEFAULT_EXTENSIONS), + default=DEFAULT_EXTENSIONS) + parser.add_argument( + '-r', + '--recursive', + action='store_true', + help='run recursively over directories') + parser.add_argument( + '-d', + '--dry-run', + action='store_true', + help='just print the list of files') + parser.add_argument( + '-i', + '--in-place', + action='store_true', + help='format file instead of printing differences') + parser.add_argument('files', metavar='file', nargs='+') + parser.add_argument( + '-q', + '--quiet', + action='store_true', + help="disable output, useful for the exit code") + parser.add_argument( + '-j', + metavar='N', + type=int, + default=0, + help='run N clang-format jobs in parallel' + ' (default number of cpus + 1)') + parser.add_argument( + '--color', + default='auto', + choices=['auto', 'always', 'never'], + help='show colored diff (default: auto)') + parser.add_argument( + '-e', + '--exclude', + metavar='PATTERN', + action='append', + default=[], + help='exclude paths matching the given glob-like pattern(s)' + ' from recursive search') + parser.add_argument( + '--style', + help='formatting style to apply (LLVM, Google, Chromium, Mozilla, WebKit)') + + args = parser.parse_args() + + # use default signal handling, like diff return SIGINT value on ^C + # https://bugs.python.org/issue14229#msg156446 + signal.signal(signal.SIGINT, signal.SIG_DFL) + try: + signal.SIGPIPE + except AttributeError: + # compatibility, SIGPIPE does not exist on Windows + pass + else: + signal.signal(signal.SIGPIPE, signal.SIG_DFL) + + colored_stdout = False + colored_stderr = False + if args.color == 'always': + colored_stdout = True + colored_stderr = True + elif args.color == 'auto': + colored_stdout = sys.stdout.isatty() + colored_stderr = sys.stderr.isatty() + + version_invocation = [args.clang_format_executable, str("--version")] + try: + subprocess.check_call(version_invocation, stdout=DEVNULL) + except subprocess.CalledProcessError as e: + print_trouble(parser.prog, str(e), use_colors=colored_stderr) + return ExitStatus.TROUBLE + except OSError as e: + print_trouble( + parser.prog, + "Command '{}' failed to start: {}".format( + subprocess.list2cmdline(version_invocation), e + ), + use_colors=colored_stderr, + ) + return ExitStatus.TROUBLE + + retcode = ExitStatus.SUCCESS + + excludes = excludes_from_file(DEFAULT_CLANG_FORMAT_IGNORE) + excludes.extend(args.exclude) + + files = list_files( + args.files, + recursive=args.recursive, + exclude=excludes, + extensions=args.extensions.split(',')) + + if not files: + return + + njobs = args.j + if njobs == 0: + njobs = multiprocessing.cpu_count() + 1 + njobs = min(len(files), njobs) + + if njobs == 1: + # execute directly instead of in a pool, + # less overhead, simpler stacktraces + it = (run_clang_format_diff_wrapper(args, file) for file in files) + pool = None + else: + pool = multiprocessing.Pool(njobs) + it = pool.imap_unordered( + partial(run_clang_format_diff_wrapper, args), files) + pool.close() + while True: + try: + outs, errs = next(it) + except StopIteration: + break + except DiffError as e: + print_trouble(parser.prog, str(e), use_colors=colored_stderr) + retcode = ExitStatus.TROUBLE + sys.stderr.writelines(e.errs) + except UnexpectedError as e: + print_trouble(parser.prog, str(e), use_colors=colored_stderr) + sys.stderr.write(e.formatted_traceback) + retcode = ExitStatus.TROUBLE + # stop at the first unexpected error, + # something could be very wrong, + # don't process all files unnecessarily + if pool: + pool.terminate() + break + else: + sys.stderr.writelines(errs) + if outs == []: + continue + if not args.quiet: + print_diff(outs, use_color=colored_stdout) + if retcode == ExitStatus.SUCCESS: + retcode = ExitStatus.DIFF + if pool: + pool.join() + return retcode + + +if __name__ == '__main__': + sys.exit(main()) \ No newline at end of file diff --git a/run-format.py b/run-format.py new file mode 100644 index 0000000..4c510ca --- /dev/null +++ b/run-format.py @@ -0,0 +1,11 @@ +import subprocess + +if __name__ == "__main__": + subprocess.call([ + 'python', + 'run-clang-format.py', '-r', 'src', 'include', 'testWnd/src', 'testWnd/include' + ]) + subprocess.call([ + 'python', + 'run-clang-format.py', '-r', '-i', 'src', 'include', 'testWnd/src', 'testWnd/include' + ]) diff --git a/src/ComBSTR.cpp b/src/ComBSTR.cpp new file mode 100644 index 0000000..767a584 --- /dev/null +++ b/src/ComBSTR.cpp @@ -0,0 +1,23 @@ +#include "tf/ComBSTR.hpp" + +namespace IngameIME::tf +{ + +ComBSTR::~ComBSTR() +{ + SysFreeString(bstr); +} + +[[nodiscard]] BSTR* ComBSTR::operator&() +{ + if (bstr) throw new std::runtime_error("Acquire address for non-null pointer"); + + return &bstr; +} + +ComBSTR::operator bool() const noexcept +{ + return bstr != nullptr; +} + +} // namespace IngameIME::tf diff --git a/src/ComException.cpp b/src/ComException.cpp new file mode 100644 index 0000000..862bd56 --- /dev/null +++ b/src/ComException.cpp @@ -0,0 +1,10 @@ +#include "tf/ComException.hpp" + +namespace IngameIME::tf +{ +ComException::ComException(HRESULT hr) + : hr(hr) + , std::runtime_error(format("ComException[0x%1!08x!]: %2!s!", hr, format(hr).c_str()).c_str()) +{ +} +} // namespace IngameIME::tf diff --git a/src/ComObjectBase.cpp b/src/ComObjectBase.cpp new file mode 100644 index 0000000..24e0d87 --- /dev/null +++ b/src/ComObjectBase.cpp @@ -0,0 +1,15 @@ +#include "tf/ComObjectBase.hpp" + +namespace IngameIME::tf +{ +ULONG ComObjectBase::InternalAddRef(void) +{ + return ++ref; +} + +ULONG ComObjectBase::InternalRelease(void) +{ + if (--ref == 0) delete this; + return ref; +} +} // namespace IngameIME::tf diff --git a/src/DebugMsg.cpp b/src/DebugMsg.cpp new file mode 100644 index 0000000..3241e42 --- /dev/null +++ b/src/DebugMsg.cpp @@ -0,0 +1,339 @@ +#include "DebugMsg.h" + +#include +#include +#include + +const std::vector IgnoredMsg = { + WM_MOVE, WM_PAINT, WM_MOUSEACTIVATE, WM_GETMINMAXINFO, WM_WINDOWPOSCHANGING, WM_WINDOWPOSCHANGED, + WM_GETICON, WM_NCACTIVATE, WM_NCLBUTTONDOWN, WM_SYSCOMMAND, WM_LBUTTONDOWN, WM_LBUTTONUP, + WM_CAPTURECHANGED, WM_MOVING, WM_ENTERSIZEMOVE, WM_EXITSIZEMOVE, WM_NCMOUSELEAVE, WM_NCHITTEST, + WM_SETCURSOR, WM_MOUSEFIRST, WM_NCMOUSEMOVE, WM_MOUSELEAVE, WM_KEYDOWN, WM_KEYUP}; + +std::map UsedMsg = {}; + +// These from https://wiki.winehq.org/List_Of_Windows_Messages +const std::map MsgMap = { + { 0, L"WM_NULL"}, + { 1, L"WM_CREATE"}, + { 2, L"WM_DESTROY"}, + { 3, L"WM_MOVE"}, + { 5, L"WM_SIZE"}, + { 6, L"WM_ACTIVATE"}, + { 7, L"WM_SETFOCUS"}, + { 8, L"WM_KILLFOCUS"}, + { 10, L"WM_ENABLE"}, + { 11, L"WM_SETREDRAW"}, + { 12, L"WM_SETTEXT"}, + { 13, L"WM_GETTEXT"}, + { 14, L"WM_GETTEXTLENGTH"}, + { 15, L"WM_PAINT"}, + { 16, L"WM_CLOSE"}, + { 17, L"WM_QUERYENDSESSION"}, + { 18, L"WM_QUIT"}, + { 19, L"WM_QUERYOPEN"}, + { 20, L"WM_ERASEBKGND"}, + { 21, L"WM_SYSCOLORCHANGE"}, + { 22, L"WM_ENDSESSION"}, + { 24, L"WM_SHOWWINDOW"}, + { 25, L"WM_CTLCOLOR"}, + { 26, L"WM_WININICHANGE"}, + { 27, L"WM_DEVMODECHANGE"}, + { 28, L"WM_ACTIVATEAPP"}, + { 29, L"WM_FONTCHANGE"}, + { 30, L"WM_TIMECHANGE"}, + { 31, L"WM_CANCELMODE"}, + { 32, L"WM_SETCURSOR"}, + { 33, L"WM_MOUSEACTIVATE"}, + { 34, L"WM_CHILDACTIVATE"}, + { 35, L"WM_QUEUESYNC"}, + { 36, L"WM_GETMINMAXINFO"}, + { 38, L"WM_PAINTICON"}, + { 39, L"WM_ICONERASEBKGND"}, + { 40, L"WM_NEXTDLGCTL"}, + { 42, L"WM_SPOOLERSTATUS"}, + { 43, L"WM_DRAWITEM"}, + { 44, L"WM_MEASUREITEM"}, + { 45, L"WM_DELETEITEM"}, + { 46, L"WM_VKEYTOITEM"}, + { 47, L"WM_CHARTOITEM"}, + { 48, L"WM_SETFONT"}, + { 49, L"WM_GETFONT"}, + { 50, L"WM_SETHOTKEY"}, + { 51, L"WM_GETHOTKEY"}, + { 55, L"WM_QUERYDRAGICON"}, + { 57, L"WM_COMPAREITEM"}, + { 61, L"WM_GETOBJECT"}, + { 65, L"WM_COMPACTING"}, + { 68, L"WM_COMMNOTIFY"}, + { 70, L"WM_WINDOWPOSCHANGING"}, + { 71, L"WM_WINDOWPOSCHANGED"}, + { 72, L"WM_POWER"}, + { 73, L"WM_COPYGLOBALDATA"}, + { 74, L"WM_COPYDATA"}, + { 75, L"WM_CANCELJOURNAL"}, + { 78, L"WM_NOTIFY"}, + { 80, L"WM_INPUTLANGCHANGEREQUEST"}, + { 81, L"WM_INPUTLANGCHANGE"}, + { 82, L"WM_TCARD"}, + { 83, L"WM_HELP"}, + { 84, L"WM_USERCHANGED"}, + { 85, L"WM_NOTIFYFORMAT"}, + { 123, L"WM_CONTEXTMENU"}, + { 124, L"WM_STYLECHANGING"}, + { 125, L"WM_STYLECHANGED"}, + { 126, L"WM_DISPLAYCHANGE"}, + { 127, L"WM_GETICON"}, + { 128, L"WM_SETICON"}, + { 129, L"WM_NCCREATE"}, + { 130, L"WM_NCDESTROY"}, + { 131, L"WM_NCCALCSIZE"}, + { 132, L"WM_NCHITTEST"}, + { 133, L"WM_NCPAINT"}, + { 134, L"WM_NCACTIVATE"}, + { 135, L"WM_GETDLGCODE"}, + { 136, L"WM_SYNCPAINT"}, + { 160, L"WM_NCMOUSEMOVE"}, + { 161, L"WM_NCLBUTTONDOWN"}, + { 162, L"WM_NCLBUTTONUP"}, + { 163, L"WM_NCLBUTTONDBLCLK"}, + { 164, L"WM_NCRBUTTONDOWN"}, + { 165, L"WM_NCRBUTTONUP"}, + { 166, L"WM_NCRBUTTONDBLCLK"}, + { 167, L"WM_NCMBUTTONDOWN"}, + { 168, L"WM_NCMBUTTONUP"}, + { 169, L"WM_NCMBUTTONDBLCLK"}, + { 171, L"WM_NCXBUTTONDOWN"}, + { 172, L"WM_NCXBUTTONUP"}, + { 173, L"WM_NCXBUTTONDBLCLK"}, + { 176, L"EM_GETSEL"}, + { 177, L"EM_SETSEL"}, + { 178, L"EM_GETRECT"}, + { 179, L"EM_SETRECT"}, + { 180, L"EM_SETRECTNP"}, + { 181, L"EM_SCROLL"}, + { 182, L"EM_LINESCROLL"}, + { 183, L"EM_SCROLLCARET"}, + { 185, L"EM_GETMODIFY"}, + { 187, L"EM_SETMODIFY"}, + { 188, L"EM_GETLINECOUNT"}, + { 189, L"EM_LINEINDEX"}, + { 190, L"EM_SETHANDLE"}, + { 191, L"EM_GETHANDLE"}, + { 192, L"EM_GETTHUMB"}, + { 193, L"EM_LINELENGTH"}, + { 194, L"EM_REPLACESEL"}, + { 195, L"EM_SETFONT"}, + { 196, L"EM_GETLINE"}, + { 197, L"EM_LIMITTEXT"}, + { 197, L"EM_SETLIMITTEXT"}, + { 198, L"EM_CANUNDO"}, + { 199, L"EM_UNDO"}, + { 200, L"EM_FMTLINES"}, + { 201, L"EM_LINEFROMCHAR"}, + { 202, L"EM_SETWORDBREAK"}, + { 203, L"EM_SETTABSTOPS"}, + { 204, L"EM_SETPASSWORDCHAR"}, + { 205, L"EM_EMPTYUNDOBUFFER"}, + { 206, L"EM_GETFIRSTVISIBLELINE"}, + { 207, L"EM_SETREADONLY"}, + { 209, L"EM_SETWORDBREAKPROC"}, + { 209, L"EM_GETWORDBREAKPROC"}, + { 210, L"EM_GETPASSWORDCHAR"}, + { 211, L"EM_SETMARGINS"}, + { 212, L"EM_GETMARGINS"}, + { 213, L"EM_GETLIMITTEXT"}, + { 214, L"EM_POSFROMCHAR"}, + { 215, L"EM_CHARFROMPOS"}, + { 216, L"EM_SETIMESTATUS"}, + { 217, L"EM_GETIMESTATUS"}, + { 224, L"SBM_SETPOS"}, + { 225, L"SBM_GETPOS"}, + { 226, L"SBM_SETRANGE"}, + { 227, L"SBM_GETRANGE"}, + { 228, L"SBM_ENABLE_ARROWS"}, + { 230, L"SBM_SETRANGEREDRAW"}, + { 233, L"SBM_SETSCROLLINFO"}, + { 234, L"SBM_GETSCROLLINFO"}, + { 235, L"SBM_GETSCROLLBARINFO"}, + { 240, L"BM_GETCHECK"}, + { 241, L"BM_SETCHECK"}, + { 242, L"BM_GETSTATE"}, + { 243, L"BM_SETSTATE"}, + { 244, L"BM_SETSTYLE"}, + { 245, L"BM_CLICK"}, + { 246, L"BM_GETIMAGE"}, + { 247, L"BM_SETIMAGE"}, + { 248, L"BM_SETDONTCLICK"}, + { 255, L"WM_INPUT"}, + { 256, L"WM_KEYDOWN"}, + { 256, L"WM_KEYFIRST"}, + { 257, L"WM_KEYUP"}, + { 258, L"WM_CHAR"}, + { 259, L"WM_DEADCHAR"}, + { 260, L"WM_SYSKEYDOWN"}, + { 261, L"WM_SYSKEYUP"}, + { 262, L"WM_SYSCHAR"}, + { 263, L"WM_SYSDEADCHAR"}, + { 264, L"WM_KEYLAST"}, + { 265, L"WM_UNICHAR"}, + { 265, L"WM_WNT_CONVERTREQUESTEX"}, + { 266, L"WM_CONVERTREQUEST"}, + { 267, L"WM_CONVERTRESULT"}, + { 268, L"WM_INTERIM"}, + { 269, L"WM_IME_STARTCOMPOSITION"}, + { 270, L"WM_IME_ENDCOMPOSITION"}, + { 271, L"WM_IME_COMPOSITION"}, + { 271, L"WM_IME_KEYLAST"}, + { 272, L"WM_INITDIALOG"}, + { 273, L"WM_COMMAND"}, + { 274, L"WM_SYSCOMMAND"}, + { 275, L"WM_TIMER"}, + { 276, L"WM_HSCROLL"}, + { 277, L"WM_VSCROLL"}, + { 278, L"WM_INITMENU"}, + { 279, L"WM_INITMENUPOPUP"}, + { 280, L"WM_SYSTIMER"}, + { 287, L"WM_MENUSELECT"}, + { 288, L"WM_MENUCHAR"}, + { 289, L"WM_ENTERIDLE"}, + { 290, L"WM_MENURBUTTONUP"}, + { 291, L"WM_MENUDRAG"}, + { 292, L"WM_MENUGETOBJECT"}, + { 293, L"WM_UNINITMENUPOPUP"}, + { 294, L"WM_MENUCOMMAND"}, + { 295, L"WM_CHANGEUISTATE"}, + { 296, L"WM_UPDATEUISTATE"}, + { 297, L"WM_QUERYUISTATE"}, + { 306, L"WM_CTLCOLORMSGBOX"}, + { 307, L"WM_CTLCOLOREDIT"}, + { 308, L"WM_CTLCOLORLISTBOX"}, + { 309, L"WM_CTLCOLORBTN"}, + { 310, L"WM_CTLCOLORDLG"}, + { 311, L"WM_CTLCOLORSCROLLBAR"}, + { 312, L"WM_CTLCOLORSTATIC"}, + { 512, L"WM_MOUSEFIRST"}, + { 512, L"WM_MOUSEMOVE"}, + { 513, L"WM_LBUTTONDOWN"}, + { 514, L"WM_LBUTTONUP"}, + { 515, L"WM_LBUTTONDBLCLK"}, + { 516, L"WM_RBUTTONDOWN"}, + { 517, L"WM_RBUTTONUP"}, + { 518, L"WM_RBUTTONDBLCLK"}, + { 519, L"WM_MBUTTONDOWN"}, + { 520, L"WM_MBUTTONUP"}, + { 521, L"WM_MBUTTONDBLCLK"}, + { 521, L"WM_MOUSELAST"}, + { 522, L"WM_MOUSEWHEEL"}, + { 523, L"WM_XBUTTONDOWN"}, + { 524, L"WM_XBUTTONUP"}, + { 525, L"WM_XBUTTONDBLCLK"}, + { 528, L"WM_PARENTNOTIFY"}, + { 529, L"WM_ENTERMENULOOP"}, + { 530, L"WM_EXITMENULOOP"}, + { 531, L"WM_NEXTMENU"}, + { 532, L"WM_SIZING"}, + { 533, L"WM_CAPTURECHANGED"}, + { 534, L"WM_MOVING"}, + { 536, L"WM_POWERBROADCAST"}, + { 537, L"WM_DEVICECHANGE"}, + { 544, L"WM_MDICREATE"}, + { 545, L"WM_MDIDESTROY"}, + { 546, L"WM_MDIACTIVATE"}, + { 547, L"WM_MDIRESTORE"}, + { 548, L"WM_MDINEXT"}, + { 549, L"WM_MDIMAXIMIZE"}, + { 550, L"WM_MDITILE"}, + { 551, L"WM_MDICASCADE"}, + { 552, L"WM_MDIICONARRANGE"}, + { 553, L"WM_MDIGETACTIVE"}, + { 560, L"WM_MDISETMENU"}, + { 561, L"WM_ENTERSIZEMOVE"}, + { 562, L"WM_EXITSIZEMOVE"}, + { 563, L"WM_DROPFILES"}, + { 564, L"WM_MDIREFRESHMENU"}, + { 640, L"WM_IME_REPORT"}, + { 641, L"WM_IME_SETCONTEXT"}, + { 642, L"WM_IME_NOTIFY"}, + { 643, L"WM_IME_CONTROL"}, + { 644, L"WM_IME_COMPOSITIONFULL"}, + { 645, L"WM_IME_SELECT"}, + { 646, L"WM_IME_CHAR"}, + { 648, L"WM_IME_REQUEST"}, + { 656, L"WM_IMEKEYDOWN"}, + { 656, L"WM_IME_KEYDOWN"}, + { 657, L"WM_IMEKEYUP"}, + { 657, L"WM_IME_KEYUP"}, + { 672, L"WM_NCMOUSEHOVER"}, + { 673, L"WM_MOUSEHOVER"}, + { 674, L"WM_NCMOUSELEAVE"}, + { 675, L"WM_MOUSELEAVE"}, + { 768, L"WM_CUT"}, + { 769, L"WM_COPY"}, + { 770, L"WM_PASTE"}, + { 771, L"WM_CLEAR"}, + { 772, L"WM_UNDO"}, + { 773, L"WM_RENDERFORMAT"}, + { 774, L"WM_RENDERALLFORMATS"}, + { 775, L"WM_DESTROYCLIPBOARD"}, + { 776, L"WM_DRAWCLIPBOARD"}, + { 777, L"WM_PAINTCLIPBOARD"}, + { 778, L"WM_VSCROLLCLIPBOARD"}, + { 779, L"WM_SIZECLIPBOARD"}, + { 780, L"WM_ASKCBFORMATNAME"}, + { 781, L"WM_CHANGECBCHAIN"}, + { 782, L"WM_HSCROLLCLIPBOARD"}, + { 783, L"WM_QUERYNEWPALETTE"}, + { 784, L"WM_PALETTEISCHANGING"}, + { 785, L"WM_PALETTECHANGED"}, + { 786, L"WM_HOTKEY"}, + { 791, L"WM_PRINT"}, + { 792, L"WM_PRINTCLIENT"}, + { 793, L"WM_APPCOMMAND"}, + { 856, L"WM_HANDHELDFIRST"}, + { 863, L"WM_HANDHELDLAST"}, + { 864, L"WM_AFXFIRST"}, + { 895, L"WM_AFXLAST"}, + { 896, L"WM_PENWINFIRST"}, + { 897, L"WM_RCRESULT"}, + { 898, L"WM_HOOKRCRESULT"}, + { 899, L"WM_GLOBALRCCHANGE"}, + { 899, L"WM_PENMISCINFO"}, + { 900, L"WM_SKB"}, + { 901, L"WM_HEDITCTL"}, + { 901, L"WM_PENCTL"}, + { 902, L"WM_PENMISC"}, + { 903, L"WM_CTLINIT"}, + { 904, L"WM_PENEVENT"}, + { 911, L"WM_PENWINLAST"}, + {1024, L"WM_USER"} +}; + +std::wstring GetMessageText(UINT msg) noexcept +{ + UsedMsg[msg]++; + + auto iter = MsgMap.find(msg); + if (iter == MsgMap.end()) + return L""; + else + return (*iter).second; +} + +void ShowUsedMsg() noexcept +{ + for (auto&& kv : UsedMsg) + { + wprintf(L"[%04x](%s): %zu\n", kv.first, GetMessageText(kv.first).c_str(), kv.second); + } +} + +void ShowMessageText(UINT msg) noexcept +{ + // Dont show Ignored msgs + if (std::find(IgnoredMsg.begin(), IgnoredMsg.end(), msg) == IgnoredMsg.end()) + { + auto text = GetMessageText(msg); + if (!text.empty()) wprintf(L"WndProc: [%04x](%s)\n", msg, text.c_str()); + } +} diff --git a/src/include/FormatUtil.hpp b/src/FormatUtil.cpp similarity index 62% rename from src/include/FormatUtil.hpp rename to src/FormatUtil.cpp index 3582bc0..1f613a5 100644 --- a/src/include/FormatUtil.hpp +++ b/src/FormatUtil.cpp @@ -1,7 +1,8 @@ -#pragma once -#include +#include "FormatUtil.hpp" -#include +namespace IngameIME + +{ std::string format(const HRESULT hr) { @@ -21,38 +22,50 @@ std::string format(const HRESULT hr) return result; } -std::string format(const char* pMessage, ...) +std::wstring format(const wchar_t* pMessage, ...) { va_list args = NULL; va_start(args, pMessage); - LPSTR pBufMsg = NULL; + LPWSTR pBufMsg = NULL; - FormatMessageA( - FORMAT_MESSAGE_FROM_STRING | FORMAT_MESSAGE_ALLOCATE_BUFFER, pMessage, 0, 0, (LPSTR)&pBufMsg, 0, &args); + FormatMessageW(FORMAT_MESSAGE_FROM_STRING | FORMAT_MESSAGE_ALLOCATE_BUFFER, + pMessage, + 0, + 0, + (LPWSTR)&pBufMsg, + 0, + &args); va_end(args); - auto result = std::string(pBufMsg); + auto result = std::wstring(pBufMsg); LocalFree(pBufMsg); return result; } -std::wstring format(const wchar_t* pMessage, ...) +std::string format(const char* pMessage, ...) { va_list args = NULL; va_start(args, pMessage); - LPWSTR pBufMsg = NULL; + LPSTR pBufMsg = NULL; - FormatMessageW( - FORMAT_MESSAGE_FROM_STRING | FORMAT_MESSAGE_ALLOCATE_BUFFER, pMessage, 0, 0, (LPWSTR)&pBufMsg, 0, &args); + FormatMessageA(FORMAT_MESSAGE_FROM_STRING | FORMAT_MESSAGE_ALLOCATE_BUFFER, + pMessage, + 0, + 0, + (LPSTR)&pBufMsg, + 0, + &args); va_end(args); - auto result = std::wstring(pBufMsg); + auto result = std::string(pBufMsg); LocalFree(pBufMsg); return result; -} \ No newline at end of file +} + +} // namespace IngameIME diff --git a/src/IThreadAssociate.cpp b/src/IThreadAssociate.cpp new file mode 100644 index 0000000..175a405 --- /dev/null +++ b/src/IThreadAssociate.cpp @@ -0,0 +1,23 @@ +#include "tf/IThreadAssociate.hpp" + +namespace IngameIME::tf + +{ + +DWORD IThreadAssociate::initialCreatorThread(const DWORD threadId) +{ + return this->threadId = (threadId != 0 ? threadId : GetCurrentThreadId()); +} + +HRESULT IThreadAssociate::assertCreatorThread() const +{ + if (GetCurrentThreadId() != threadId) return UI_E_WRONG_THREAD; + return S_OK; +} + +DWORD IThreadAssociate::getCreatorThreadId() const +{ + return threadId; +} + +} // namespace IngameIME::tf diff --git a/src/ImmCompositionImpl.cpp b/src/ImmCompositionImpl.cpp new file mode 100644 index 0000000..c17ffc7 --- /dev/null +++ b/src/ImmCompositionImpl.cpp @@ -0,0 +1,15 @@ +#include "imm/ImmCompositionImpl.hpp" + +namespace IngameIME::imm +{ +CompositionImpl::CompositionImpl(InputContextImpl* inputCtx) + : inputCtx(inputCtx) +{ +} + +void CompositionImpl::terminate() noexcept +{ + ImmNotifyIME(inputCtx->ctx, NI_COMPOSITIONSTR, CPS_COMPLETE, 0); +} + +} // namespace IngameIME::imm diff --git a/src/ImmIngameIMEImpl.cpp b/src/ImmIngameIMEImpl.cpp new file mode 100644 index 0000000..3ed96ed --- /dev/null +++ b/src/ImmIngameIMEImpl.cpp @@ -0,0 +1,21 @@ +#include "imm/ImmIngameIMEImpl.hpp" + +namespace IngameIME::imm + +{ + +std::shared_ptr GlobalImpl::getActiveInputProcessor() const +{ + return InputProcessorImpl::getActiveInputProcessor(); +} + +std::list> GlobalImpl::getInputProcessors() const +{ + return InputProcessorImpl::getInputProcessors(); +} + +std::shared_ptr GlobalImpl::getInputContext(void* hWnd, ...) +{ + return InputContextImpl::getOrCreate(reinterpret_cast(hWnd)); +} +} // namespace IngameIME::imm diff --git a/src/ImmInputContextImpl.cpp b/src/ImmInputContextImpl.cpp new file mode 100644 index 0000000..3061722 --- /dev/null +++ b/src/ImmInputContextImpl.cpp @@ -0,0 +1,258 @@ +#include "imm/ImmInputContextImpl.hpp" + +#include "imm/ImmCompositionImpl.hpp" + +IngameIME::imm::InputContextImpl::Singleton::RefHolderType IngameIME::imm::InputContextImpl::WeakRefs = {}; +std::mutex IngameIME::imm::InputContextImpl::RefHolderMutex = std::mutex(); + +namespace IngameIME::imm +{ +LRESULT InputContextImpl::WndProc(HWND hWnd, UINT msg, WPARAM wparam, LPARAM lparam) +{ + auto iter = WeakRefs.find(hWnd); + + std::shared_ptr inputCtx; + if (iter != WeakRefs.end() && (inputCtx = (*iter).second.lock())) + { + switch (msg) + { + case WM_INPUTLANGCHANGE: + Global::getInstance().runCallback(InputProcessorState::FullUpdate, inputCtx->getInputProcCtx()); + break; + case WM_IME_SETCONTEXT: + // We should always hide Composition Window to make the + // PreEditCallback work + lparam &= ~ISC_SHOWUICOMPOSITIONWINDOW; + + if (inputCtx->fullscreen) + { + // Hide Candidate Window + lparam &= ~ISC_SHOWUICANDIDATEWINDOW; + } + break; + case WM_IME_STARTCOMPOSITION: + inputCtx->comp->PreEditCallbackHolder::runCallback(CompositionState::Begin, nullptr); + return true; + case WM_IME_COMPOSITION: + if (lparam & (GCS_COMPSTR | GCS_CURSORPOS)) inputCtx->procPreEdit(); + if (lparam & GCS_RESULTSTR) inputCtx->procCommit(); + + if (!inputCtx->fullscreen) inputCtx->procPreEditRect(); + + // when lparam == 0 that means current Composition has been + // canceled + if (lparam) return true; + case WM_IME_ENDCOMPOSITION: + inputCtx->comp->PreEditCallbackHolder::runCallback(CompositionState::End, nullptr); + inputCtx->comp->CandidateListCallbackHolder::runCallback(CandidateListState::End, nullptr); + return true; + case WM_IME_NOTIFY: + if (inputCtx->fullscreen) switch (wparam) + { + case IMN_OPENCANDIDATE: + inputCtx->comp->CandidateListCallbackHolder::runCallback(CandidateListState::Begin, nullptr); + return true; + case IMN_CHANGECANDIDATE: + inputCtx->procCand(); + return true; + case IMN_CLOSECANDIDATE: + inputCtx->comp->CandidateListCallbackHolder::runCallback(CandidateListState::End, nullptr); + return true; + default: + break; + } + if (wparam == IMN_SETCONVERSIONMODE) + { + Global::getInstance().runCallback(InputProcessorState::InputModeUpdate, inputCtx->getInputProcCtx()); + } + break; + case WM_IME_CHAR: + // Commit text already handled at WM_IME_COMPOSITION + return true; + default: + return CallWindowProcW(inputCtx->prevProc, hWnd, msg, wparam, lparam); + } + } + return DefWindowProcW(hWnd, msg, wparam, lparam); +} + +IngameIME::imm::InputContextImpl::InputContextImpl(const HWND hWnd) + : Singleton(hWnd) + , hWnd(hWnd) +{ + comp = std::make_shared(this); + + // Reset to default context + ImmAssociateContextEx(hWnd, NULL, IACE_DEFAULT); + ctx = ImmAssociateContext(hWnd, NULL); + + prevProc = (WNDPROC)SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)InputContextImpl::WndProc); +} + +InputContextImpl::~InputContextImpl() +{ + comp->terminate(); + ImmAssociateContextEx(hWnd, NULL, IACE_DEFAULT); + SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)prevProc); +} + +void InputContextImpl::procPreEdit() +{ + // PreEdit Text + auto size = ImmGetCompositionStringW(ctx, GCS_COMPSTR, NULL, 0); + // Error occurs + if (size <= 0) return; + + auto buf = std::make_unique(size / sizeof(WCHAR)); + ImmGetCompositionStringW(ctx, GCS_COMPSTR, buf.get(), size); + + // Selection + int sel = ImmGetCompositionStringW(ctx, GCS_CURSORPOS, NULL, 0); + + PreEditContext ctx; + ctx.content = std::wstring(buf.get(), size / sizeof(WCHAR)); + ctx.selStart = ctx.selEnd = sel; + + comp->PreEditCallbackHolder::runCallback(CompositionState::Update, &ctx); +} + +void InputContextImpl::procCommit() +{ + auto size = ImmGetCompositionStringW(ctx, GCS_RESULTSTR, NULL, 0); + // Error occurs + if (size <= 0) return; + + auto buf = std::make_unique(size / sizeof(WCHAR)); + ImmGetCompositionStringW(ctx, GCS_RESULTSTR, buf.get(), size); + + comp->CommitCallbackHolder::runCallback(std::wstring(buf.get(), size / sizeof(WCHAR))); +} + +void InputContextImpl::procPreEditRect() +{ + InternalRect rect; + comp->PreEditRectCallbackHolder::runCallback(rect); + + CANDIDATEFORM cand; + cand.dwIndex = 0; + cand.dwStyle = CFS_EXCLUDE; + cand.ptCurrentPos.x = rect.left; + cand.ptCurrentPos.y = rect.top; + cand.rcArea = rect; + ImmSetCandidateWindow(ctx, &cand); + + COMPOSITIONFORM comp; + comp.dwStyle = CFS_RECT; + comp.ptCurrentPos.x = rect.left; + comp.ptCurrentPos.y = rect.top; + comp.rcArea = rect; + ImmSetCompositionWindow(ctx, &comp); +} + +void InputContextImpl::procCand() +{ + CandidateListContext candCtx; + + auto size = ImmGetCandidateListW(ctx, 0, NULL, 0); + // Error occurs + if (size == 0) return; + + auto buf = std::make_unique(size); + auto cand = (LPCANDIDATELIST)buf.get(); + + ImmGetCandidateListW(ctx, 0, cand, size); + + auto pageSize = cand->dwPageSize; + auto candCount = cand->dwCount; + + auto pageStart = cand->dwPageStart; + auto pageEnd = pageStart + pageSize; + candCtx.selection = cand->dwSelection; + // Absolute index to relative index + candCtx.selection -= pageStart; + + for (size_t i = 0; i < pageSize; i++) + { + auto strStart = buf.get() + cand->dwOffset[i + pageStart]; + auto strEnd = buf.get() + (((i + pageStart + 1) < candCount) ? cand->dwOffset[i + pageStart + 1] : size); + auto len = (strEnd - strStart) / sizeof(WCHAR); + + candCtx.candidates.push_back(std::wstring((wchar_t*)strStart, len)); + } + + comp->CandidateListCallbackHolder::runCallback(CandidateListState::Update, &candCtx); +} + +InputProcessorContext InputContextImpl::getInputProcCtx() +{ + InputProcessorContext result; + + DWORD mode; + ImmGetConversionStatus(ctx, &mode, NULL); + + auto activeProc = InputProcessorImpl::getActiveInputProcessor(); + + std::list modes; + if (activeProc->type == InputProcessorType::KeyboardLayout) + modes.push_back(L"AlphaNumeric"); + else + { + if (mode & IME_CMODE_NATIVE) + { + modes.push_back(L"Native"); + + if (activeProc->isJap) + if (mode & IME_CMODE_KATAKANA) + modes.push_back(L"Katakana"); + else + modes.push_back(L"Hiragana"); + } + else + modes.push_back(L"AlphaNumeric"); + + if (mode & IME_CMODE_FULLSHAPE) + modes.push_back(L"FullShape"); + else + modes.push_back(L"HalfShape"); + } + + result.proc = activeProc; + result.modes = modes; + + return result; +} + +void InputContextImpl::setActivated(const bool activated) noexcept +{ + this->activated = activated; + + if (activated) + ImmAssociateContext(hWnd, ctx); + else + ImmAssociateContext(hWnd, NULL); +} + +bool InputContextImpl::getActivated() const noexcept +{ + return activated; +} + +void InputContextImpl::setFullScreen(const bool fullscreen) noexcept +{ + this->fullscreen = fullscreen; + + if (activated) + { + comp->terminate(); + // Refresh InputContext + ImmAssociateContext(hWnd, NULL); + ImmAssociateContext(hWnd, ctx); + } +} + +bool InputContextImpl::getFullScreen() const noexcept +{ + return fullscreen; +} + +} // namespace IngameIME::imm diff --git a/src/IngameIMEImpl.cpp b/src/IngameIMEImpl.cpp index 4875dd8..724dd41 100644 --- a/src/IngameIMEImpl.cpp +++ b/src/IngameIMEImpl.cpp @@ -1,55 +1,13 @@ #include -#include "ImmCompositionImpl.hpp" -#include "ImmIngameIMEImpl.hpp" - -#include "TfCompositionImpl.hpp" -#include "TfIngameIMEImpl.hpp" - -libtf::InputContextImpl::InputContextImpl(HWND hWnd) -{ - COM_HR_BEGIN(S_OK); - - if (!(this->hWnd = reinterpret_cast(hWnd))) THR_HR(E_INVALIDARG); - if (initialCreatorThread() != GetWindowThreadProcessId(hWnd, NULL)) THR_HR(UI_E_WRONG_THREAD); - - CHECK_HR(getThreadMgr(&threadMgr)); - - ComQIPtr threadMgrEx(IID_ITfThreadMgrEx, threadMgr); - CHECK_HR(threadMgrEx->ActivateEx(&clientId, TF_TMAE_UIELEMENTENABLEDONLY)); - - CHECK_HR(threadMgr->CreateDocumentMgr(&emptyDocMgr)); - CHECK_HR(threadMgr->CreateDocumentMgr(&docMgr)); - - comp = std::make_shared(this); - CHECK_HR(docMgr->Push(ctx.get())); - - // Deactivate input contxt - setActivated(false); - - ComQIPtr source(IID_ITfSource, ctx); - owner = new InputContextImpl::ContextOwner(this); - CHECK_HR(source->AdviseSink(IID_ITfContextOwner, owner.get(), &cookie)); - - COM_HR_END(); - COM_HR_THR(); -} - -libimm::InputContextImpl::InputContextImpl(HWND hWnd) : hWnd(hWnd) -{ - comp = std::make_shared(this); - - // Reset to default context - ImmAssociateContextEx(hWnd, NULL, IACE_DEFAULT); - ctx = ImmAssociateContext(hWnd, NULL); - - prevProc = (WNDPROC)SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)InputContextImpl::WndProc); -} +#include "imm/ImmIngameIMEImpl.hpp" +#include "tf/TfIngameIMEImpl.hpp" #include + IngameIME::Global& IngameIME::Global::getInstance(void* ignore, ...) { - thread_local IngameIME::Global& Instance = IsWindows8OrGreater() ? (IngameIME::Global&)*new libtf::GlobalImpl() : - (IngameIME::Global&)*new libimm::GlobalImpl(); + thread_local IngameIME::Global& Instance = + IsWindows8OrGreater() ? (IngameIME::Global&)*new tf::GlobalImpl() : (IngameIME::Global&)*new imm::GlobalImpl(); return Instance; } \ No newline at end of file diff --git a/src/InputContextImpl.cpp b/src/InputContextImpl.cpp new file mode 100644 index 0000000..43cd9f0 --- /dev/null +++ b/src/InputContextImpl.cpp @@ -0,0 +1,15 @@ +#include "common/InputContextImpl.hpp" + +namespace IngameIME +{ +InternalRect::operator RECT() noexcept +{ + RECT rect; + rect.left = this->left; + rect.top = this->top; + rect.right = this->right; + rect.bottom = this->bottom; + + return rect; +} +} // namespace IngameIME diff --git a/src/InputProcessorImpl.cpp b/src/InputProcessorImpl.cpp new file mode 100644 index 0000000..83c758f --- /dev/null +++ b/src/InputProcessorImpl.cpp @@ -0,0 +1,265 @@ +#include "common/InputProcessorImpl.hpp" + +namespace IngameIME +{ +InternalLocale::Singleton::RefHolderType InternalLocale::WeakRefs = {}; +InputProcessorImpl::Singleton::RefHolderType InputProcessorImpl::WeakRefs = {}; +std::mutex InputProcessorImpl::RefHolderMutex = std::mutex(); +std::mutex InternalLocale::RefHolderMutex = std::mutex(); + +std::wstring InternalLocale::getLocaleString(const LANGID langid) +{ + LCID lcid = MAKELCID(langid, SORT_DEFAULT); + + int size = GetLocaleInfoW(lcid, LOCALE_SNAME, NULL, 0); + auto buf = std::make_unique(size); + + GetLocaleInfoW(lcid, LOCALE_SNAME, buf.get(), size); + + return std::wstring(buf.get(), size); +} + +std::wstring InternalLocale::getLocaleName(const LANGID langid) +{ + LCID lcid = MAKELCID(langid, SORT_DEFAULT); + + int size = GetLocaleInfoW(lcid, LOCALE_SLOCALIZEDDISPLAYNAME, NULL, 0); + auto buf = std::make_unique(size); + + GetLocaleInfoW(lcid, LOCALE_SLOCALIZEDDISPLAYNAME, buf.get(), size); + + return std::wstring(buf.get(), size); +} + +InternalLocale::InternalLocale(const LANGID langId) noexcept + : Singleton(langId) + , langId(langId) +{ + locale = getLocaleString(langId); + name = getLocaleName(langId); +} + +std::wstring InputProcessorImpl::getKeyboardLayoutName(LANGID langId) +{ + auto result = format(L"[KL: 0x%1!08x!]", langId); + + HKEY layouts; + if (ERROR_SUCCESS + == RegOpenKeyExW(HKEY_LOCAL_MACHINE, + L"SYSTEM\\CurrentControlSet\\Control\\Keyboard Layouts", + 0, + KEY_READ, + &layouts)) + { + // the key of the keyboard layout is its langid + char layoutKey[9]; + snprintf(layoutKey, 9, "%08x", langId); + + HKEY layout; + if (ERROR_SUCCESS == RegOpenKeyExA(layouts, layoutKey, 0, KEY_READ, &layout)) + { + // Get data size first + DWORD size; + if (ERROR_SUCCESS + == RegGetValueW(layout, + NULL, + L"Layout Display Name", + RRF_RT_REG_SZ | RRF_RT_REG_EXPAND_SZ | RRF_NOEXPAND, + NULL, + NULL, + &size)) + { + // Get resource key of the name + auto resKey = std::make_unique(size); + if (ERROR_SUCCESS + == RegGetValueW(layout, + NULL, + L"Layout Display Name", + RRF_RT_REG_SZ | RRF_RT_REG_EXPAND_SZ | RRF_NOEXPAND, + NULL, + resKey.get(), + &size)) + { + // Get the layout name by resource key + wchar_t layoutName[64]; + HRESULT hr; + if (SUCCEEDED(hr = SHLoadIndirectString(resKey.get(), layoutName, 64, NULL))) + { + result = std::wstring(layoutName); + } + } + } + RegCloseKey(layout); + } + RegCloseKey(layouts); + } + return result; +} + +std::wstring InputProcessorImpl::getTextServiceName(const TF_INPUTPROCESSORPROFILE& profile) +{ + auto result = format(L"[TIP: {%1!08x!-0x%1!04x!-0x%1!04x!-0x%1!04x!-0x%1!012x!}]", + profile.clsid.Data1, + profile.clsid.Data2, + profile.clsid.Data3, + profile.clsid.Data4); + + COM_HR_BEGIN(S_OK); + + tf::ComPtr inputProcessorProfiles; + CHECK_HR(tf::createInputProcessorProfiles(&inputProcessorProfiles)); + + tf::ComBSTR name; + CHECK_HR(inputProcessorProfiles->GetLanguageProfileDescription(profile.clsid, + profile.langid, + profile.guidProfile, + &name)); + + result = std::wstring(name.bstr); + + COM_HR_END(); + + return result; +} + +std::wstring InputProcessorImpl::getImmName(HKL hkl) +{ + auto size = ImmGetDescriptionW(hkl, NULL, 0) + 1; + + if (size == 0) return format(L"[IMM: %1!08x!]", hkl); + + auto buf = std::make_unique(size); + ImmGetDescriptionW(hkl, buf.get(), size); + + return std::wstring(buf.get(), size); +} + +bool InputProcessorImpl::isImm(HKL hkl) +{ + return (0xF000 & HIWORD(hkl)) == 0xE000; +} + +std::wstring InputProcessorImpl::getInputProcessorName(const TF_INPUTPROCESSORPROFILE& profile) +{ + switch (profile.dwProfileType) + { + case TF_PROFILETYPE_INPUTPROCESSOR: + return getTextServiceName(profile); + case TF_PROFILETYPE_KEYBOARDLAYOUT: + if (isImm(profile.hkl)) + return getImmName(profile.hkl); + else + return getKeyboardLayoutName(profile.langid); + default: + return L"[InputProcessor: unknown]"; + } +} + +InputProcessorImpl::InputProcessorImpl(const TF_INPUTPROCESSORPROFILE& profile) + : Singleton(profile) + , profile(profile) +{ + type = profile.dwProfileType == TF_PROFILETYPE_INPUTPROCESSOR || isImm(profile.hkl) + ? InputProcessorType::TextService + : InputProcessorType::KeyboardLayout; + name = getInputProcessorName(profile); + locale = InternalLocale::getOrCreate(profile.langid); + isJap = locale->locale.compare(0, 2, L"ja") == 0; +} + +std::shared_ptr InputProcessorImpl::getActiveInputProcessor() +{ + COM_HR_BEGIN(S_OK); + + if (!IsGUIThread(false)) THR_HR(UI_E_WRONG_THREAD); + + tf::ComPtr inputProcessorProfiles; + CHECK_HR(tf::createInputProcessorProfiles(&inputProcessorProfiles)); + tf::ComQIPtr inputProcessorMgr(IID_ITfInputProcessorProfileMgr, + inputProcessorProfiles); + + TF_INPUTPROCESSORPROFILE profile; + CHECK_HR(inputProcessorMgr->GetActiveProfile(GUID_TFCAT_TIP_KEYBOARD, &profile)); + return InputProcessorImpl::getOrCreate(profile); + + COM_HR_END(); + COM_HR_THR(); + + // Should not reach here + return nullptr; +} + +std::list> InputProcessorImpl::getInputProcessors() +{ + std::list> result; + + COM_HR_BEGIN(S_OK); + + if (!IsGUIThread(false)) break; + + tf::ComPtr profiles; + CHECK_HR(tf::createInputProcessorProfiles(&profiles)); + tf::ComQIPtr procMgr(IID_ITfInputProcessorProfileMgr, profiles); + + tf::ComPtr enumProfiles; + // Pass 0 to langid to enum all profiles + CHECK_HR(procMgr->EnumProfiles(0, &enumProfiles)); + + TF_INPUTPROCESSORPROFILE profile[1]; + while (true) + { + ULONG fetch; + CHECK_HR(enumProfiles->Next(1, profile, &fetch)); + + // Reach end + if (fetch == 0) break; + + // InputProcessor not enabled can't be activated + if (!(profile[0].dwFlags & TF_IPP_FLAG_ENABLED)) continue; + + result.push_back(InputProcessorImpl::getOrCreate(profile[0])); + } + + COM_HR_END(); + + return result; +} + +void InputProcessorImpl::setActivated() const +{ + COM_HR_BEGIN(S_OK); + + if (!IsGUIThread(false)) THR_HR(UI_E_WRONG_THREAD); + + tf::ComPtr inputProcessorProfiles; + CHECK_HR(tf::createInputProcessorProfiles(&inputProcessorProfiles)); + tf::ComQIPtr inputProcessorMgr(IID_ITfInputProcessorProfileMgr, + inputProcessorProfiles); + + CHECK_HR(inputProcessorMgr->ActivateProfile(profile.dwProfileType, + profile.langid, + profile.clsid, + profile.guidProfile, + profile.hkl, + TF_IPPMF_DONTCARECURRENTINPUTLANGUAGE)); + + COM_HR_END(); + COM_HR_THR(); +} + +bool ComparableProfile::operator<(const ComparableProfile& s2) const +{ + if (this->dwProfileType == s2.dwProfileType) + if (this->dwProfileType == TF_PROFILETYPE_KEYBOARDLAYOUT) + return this->hkl < s2.hkl; + else + return memcmp(&this->clsid, &s2.clsid, sizeof(CLSID)) < 0; + else + return this->dwProfileType < s2.dwProfileType; +} + +ComparableProfile::ComparableProfile(const TF_INPUTPROCESSORPROFILE& profile) +{ + memcpy(this, &profile, sizeof(profile)); +} +} // namespace IngameIME diff --git a/src/TfCompositionImpl.cpp b/src/TfCompositionImpl.cpp new file mode 100644 index 0000000..54a920f --- /dev/null +++ b/src/TfCompositionImpl.cpp @@ -0,0 +1,277 @@ +#include "tf/TfCompositionImpl.hpp" + +namespace IngameIME::tf + +{ + +CompositionImpl::CompositionHandler::CompositionHandler(CompositionImpl* comp) + : comp(comp) +{ + eleMgr = comp->inputCtx->threadMgr; +} + +HRESULT STDMETHODCALLTYPE CompositionImpl::CompositionHandler::OnStartComposition(ITfCompositionView* pComposition, + BOOL* pfOk) +{ + if (!pfOk) return E_INVALIDARG; + + // Always allow Composition start + *pfOk = true; + + comp->PreEditCallbackHolder::runCallback(CompositionState::Begin, nullptr); + + return S_OK; +} + +HRESULT STDMETHODCALLTYPE CompositionImpl::CompositionHandler::OnUpdateComposition(ITfCompositionView* pComposition, + ITfRange* pRangeNew) +{ + // Handle preedit in ITfTextEditSink + compView = pComposition; + + return S_OK; +} + +HRESULT STDMETHODCALLTYPE CompositionImpl::CompositionHandler::OnEndComposition(ITfCompositionView* pComposition) +{ + compView.reset(); + comp->PreEditCallbackHolder::runCallback(CompositionState::End, nullptr); + + static HRESULT hr; + hr = comp->inputCtx->ctx->RequestEditSession(comp->inputCtx->clientId, this, TF_ES_ASYNC | TF_ES_READWRITE, &hr); + + return S_OK; +} + +HRESULT STDMETHODCALLTYPE CompositionImpl::CompositionHandler::OnEndEdit(ITfContext* pic, + TfEditCookie ec, + ITfEditRecord* pEditRecord) +{ + COM_HR_BEGIN(S_OK); + + // No active composition + if (!compView) return S_OK; + + ComPtr preEditRange; + CHECK_HR(compView->GetRange(&preEditRange)); + + // Get preedit length + ComQIPtr rangeAcp(IID_ITfRangeACP, preEditRange); + LONG acpStart, len; + CHECK_HR(rangeAcp->GetExtent(&acpStart, &len)); + ULONG preEditLen = len; + auto bufPreEdit = std::make_unique(preEditLen); + // Get preedit text + CHECK_HR(preEditRange->GetText(ec, 0, bufPreEdit.get(), preEditLen, &preEditLen)); + + // Get selection of the preedit + TF_SELECTION sel[1]; + ULONG fetched; + ComPtr selRange; + CHECK_HR(comp->inputCtx->ctx->GetSelection(ec, TF_DEFAULT_SELECTION, 1, sel, &fetched)); + selRange.attach(sel[0].range); + rangeAcp = selRange; + CHECK_HR(rangeAcp->GetExtent(&acpStart, &len)); + + PreEditContext preEditCtx; + preEditCtx.selStart = acpStart; + preEditCtx.selEnd = acpStart + len; + preEditCtx.content = std::wstring(bufPreEdit.get(), preEditLen); + + comp->PreEditCallbackHolder::runCallback(CompositionState::Update, &preEditCtx); + + COM_HR_END(); + COM_HR_RET(); +} + +HRESULT STDMETHODCALLTYPE CompositionImpl::CompositionHandler::DoEditSession(TfEditCookie ec) +{ + COM_HR_BEGIN(S_OK); + + auto inputCtx = comp->inputCtx; + auto ctx = inputCtx->ctx; + + // Get a range which covers all the texts in the context + ComPtr fullRange; + ComPtr rangeAtEnd; + CHECK_HR(ctx->GetStart(ec, &fullRange)); + CHECK_HR(ctx->GetEnd(ec, &rangeAtEnd)); + CHECK_HR(fullRange->ShiftEndToRange(ec, rangeAtEnd.get(), TF_ANCHOR_END)); + + // It's possible that the context is empty when there is no commit + BOOL isEmpty; + CHECK_HR(fullRange->IsEmpty(ec, &isEmpty)); + if (isEmpty) return S_OK; + + // Get the text length + ComQIPtr rangeAcp(IID_ITfRangeACP, fullRange); + LONG acpStart, len; + CHECK_HR(rangeAcp->GetExtent(&acpStart, &len)); + ULONG commitLen = len; + auto bufCommit = std::make_unique(commitLen); + // Get the commit text + CHECK_HR(fullRange->GetText(ec, 0, bufCommit.get(), commitLen, &commitLen)); + // Clear the texts in the text store + CHECK_HR(fullRange->SetText(ec, 0, NULL, 0)); + + comp->CommitCallbackHolder::runCallback(std::wstring(bufCommit.get(), commitLen)); + + COM_HR_END(); + COM_HR_RET(); +} + +HRESULT STDMETHODCALLTYPE CompositionImpl::CompositionHandler::BeginUIElement(DWORD dwUIElementId, BOOL* pbShow) +{ + COM_HR_BEGIN(S_OK); + + if (dwUIElementId == TF_INVALID_UIELEMENTID) return E_INVALIDARG; + if (!pbShow) return E_INVALIDARG; + + auto inputCtx = comp->inputCtx; + + *pbShow = !inputCtx->fullscreen; + + ComPtr uiEle; + CHECK_HR(eleMgr->GetUIElement(dwUIElementId, &uiEle)); + + ComQIPtr candEle(IID_ITfCandidateListUIElement, uiEle); + // Check if current UIElement is CandidateListUIElement + if (candEle) + { + ele = candEle; + eleId = dwUIElementId; + // Handle Candidate List events + comp->CandidateListCallbackHolder::runCallback(CandidateListState::Begin, nullptr); + } + + COM_HR_END(); + COM_HR_RET(); +} + +HRESULT STDMETHODCALLTYPE CompositionImpl::CompositionHandler::UpdateUIElement(DWORD dwUIElementId) +{ + COM_HR_BEGIN(S_OK); + + if (dwUIElementId == TF_INVALID_UIELEMENTID) return E_INVALIDARG; + if (eleId == TF_INVALID_UIELEMENTID || dwUIElementId != eleId) return S_OK; + + // Total count of Candidates + uint32_t totalCount; + CHECK_HR(ele->GetCount(&totalCount)); + + // How many pages? + uint32_t pageCount; + CHECK_HR(ele->GetPageIndex(NULL, 0, &pageCount)); + + // Array of pages' start index + auto pageStarts = std::make_unique(pageCount); + CHECK_HR(ele->GetPageIndex(pageStarts.get(), pageCount, &pageCount)); + + // Current page's index in pageStarts + uint32_t curPage; + CHECK_HR(ele->GetCurrentPage(&curPage)); + + uint32_t pageStart = pageStarts[curPage]; + uint32_t pageEnd = curPage == pageCount - 1 ? totalCount : pageStarts[curPage + 1]; + uint32_t pageSize = pageEnd - pageStart; + + CandidateListContext candCtx; + + // Currently Selected Candidate's absolute index + UINT sel; + CHECK_HR(ele->GetSelection(&sel)); + // Absolute index to relative index + sel -= pageStart; + candCtx.selection = sel; + + // Get Candidate Strings + for (uint32_t i = pageStart; i < pageEnd; i++) + { + ComBSTR candidate; + if (FAILED(ele->GetString(i, &candidate))) + candCtx.candidates.push_back(L"[err]"); + else + candCtx.candidates.push_back(candidate.bstr); + } + + comp->CandidateListCallbackHolder::runCallback(CandidateListState::Update, &candCtx); + + COM_HR_END(); + COM_HR_RET(); +} + +HRESULT STDMETHODCALLTYPE CompositionImpl::CompositionHandler::EndUIElement(DWORD dwUIElementId) +{ + COM_HR_BEGIN(S_OK); + + if (dwUIElementId == TF_INVALID_UIELEMENTID) return E_INVALIDARG; + if (eleId == TF_INVALID_UIELEMENTID || dwUIElementId != eleId) return S_OK; + + eleId = TF_INVALID_UIELEMENTID; + ele.reset(); + + comp->CandidateListCallbackHolder::runCallback(CandidateListState::End, nullptr); + + COM_HR_END(); + COM_HR_RET(); +} + +CompositionImpl::CompositionImpl(InputContextImpl* inputCtx) + : inputCtx(inputCtx) +{ + COM_HR_BEGIN(S_OK); + + handler = new CompositionHandler(this); + + ComQIPtr eleMgr(IID_ITfUIElementMgr, inputCtx->threadMgr); + ComQIPtr source(IID_ITfSource, eleMgr); + CHECK_HR(source->AdviseSink(IID_ITfUIElementSink, static_cast(handler.get()), &cookieEleSink)); + + // This EditCookie is useless + TfEditCookie ec; + CHECK_HR(inputCtx->docMgr->CreateContext(inputCtx->clientId, + 0, + static_cast(handler.get()), + &inputCtx->ctx, + &ec)); + + source = inputCtx->ctx; + CHECK_HR(source->AdviseSink(IID_ITfTextEditSink, static_cast(handler.get()), &cookieEditSink)); + + COM_HR_END(); + COM_HR_THR(); +} + +CompositionImpl::~CompositionImpl() +{ + if (cookieEleSink != TF_INVALID_COOKIE) + { + ComQIPtr eleMgr(IID_ITfUIElementMgr, inputCtx->threadMgr); + ComQIPtr source(IID_ITfSource, eleMgr); + source->UnadviseSink(cookieEleSink); + cookieEleSink = TF_INVALID_COOKIE; + } + + if (cookieEditSink != TF_INVALID_COOKIE) + { + ComQIPtr source(IID_ITfSource, inputCtx->ctx); + source->UnadviseSink(cookieEditSink); + cookieEditSink = TF_INVALID_COOKIE; + } +} + +void CompositionImpl::terminate() +{ + COM_HR_BEGIN(S_OK); + + CHECK_HR(inputCtx->assertCreatorThread()); + + ComQIPtr services(IID_ITfContextOwnerCompositionServices, inputCtx->ctx); + // Pass Null to terminate all the composition + services->TerminateComposition(NULL); + + COM_HR_END(); + COM_HR_THR(); +} + +} // namespace IngameIME::tf diff --git a/src/TfFunction.cpp b/src/TfFunction.cpp new file mode 100644 index 0000000..d642d06 --- /dev/null +++ b/src/TfFunction.cpp @@ -0,0 +1,45 @@ +#include "tf/TfFunction.hpp" + +namespace IngameIME::tf + +{ + +auto getMsCtf() +{ + return std::unique_ptr, decltype(&::FreeLibrary)>(LoadLibraryW(L"msctf.dll"), + ::FreeLibrary); +} + +HRESULT createThreadMgr(ITfThreadMgr** pptim) +{ + auto hMsCtf = getMsCtf(); + if (!hMsCtf) return E_FAIL; + auto pfn = (pTF_CreateThreadMgr)GetProcAddress(hMsCtf.get(), "TF_CreateThreadMgr"); + + return pfn ? (*pfn)(pptim) : E_FAIL; +} + +HRESULT getThreadMgr(ITfThreadMgr** pptim) +{ + auto hMsCtf = getMsCtf(); + if (!hMsCtf) return E_FAIL; + auto pfn = (pTF_GetThreadMgr)GetProcAddress(hMsCtf.get(), "TF_GetThreadMgr"); + if (!pfn) return E_FAIL; + + HRESULT hr; + + if (SUCCEEDED(hr = (*pfn)(pptim))) return hr; + + return createThreadMgr(pptim); +} + +HRESULT createInputProcessorProfiles(ITfInputProcessorProfiles** ppipr) +{ + auto hMsCtf = getMsCtf(); + if (!hMsCtf) return E_FAIL; + auto pfn = (pTF_CreateInputProcessorProfiles)GetProcAddress(hMsCtf.get(), "TF_CreateInputProcessorProfiles"); + + return pfn ? (*pfn)(ppipr) : E_FAIL; +} + +} // namespace IngameIME::tf diff --git a/src/TfIngameIMEImpl.cpp b/src/TfIngameIMEImpl.cpp new file mode 100644 index 0000000..320439a --- /dev/null +++ b/src/TfIngameIMEImpl.cpp @@ -0,0 +1,59 @@ +#include "tf/TfIngameIMEImpl.hpp" + +namespace IngameIME::tf + +{ + +GlobalImpl::GlobalImpl() +{ + COM_HR_BEGIN(S_OK); + + handler = new InputProcessorHandler(); + + ComQIPtr source(IID_ITfSource, handler->mode); + CHECK_HR(source->AdviseSink(IID_ITfCompartmentEventSink, + static_cast(handler.get()), + &cookieComp)); + + source = handler->compMgr; + CHECK_HR(source->AdviseSink(IID_ITfInputProcessorProfileActivationSink, + static_cast(handler.get()), + &cookieProc)); + + COM_HR_END(); + COM_HR_THR(); +} + +GlobalImpl::~GlobalImpl() +{ + if (cookieProc != TF_INVALID_COOKIE) + { + ComQIPtr source(IID_ITfSource, handler); + source->UnadviseSink(cookieProc); + cookieProc = TF_INVALID_COOKIE; + } + + if (cookieComp != TF_INVALID_COOKIE) + { + ComQIPtr source(IID_ITfSource, handler->mode); + source->UnadviseSink(cookieComp); + cookieComp = TF_INVALID_COOKIE; + } +} + +std::shared_ptr GlobalImpl::getActiveInputProcessor() const +{ + return InputProcessorImpl::getActiveInputProcessor(); +} + +std::list> GlobalImpl::getInputProcessors() const +{ + return InputProcessorImpl::getInputProcessors(); +} + +std::shared_ptr GlobalImpl::getInputContext(void* hWnd, ...) +{ + return std::make_shared(reinterpret_cast(hWnd)); +} + +} // namespace IngameIME::tf diff --git a/src/TfInputContextImpl.cpp b/src/TfInputContextImpl.cpp new file mode 100644 index 0000000..8e045cd --- /dev/null +++ b/src/TfInputContextImpl.cpp @@ -0,0 +1,169 @@ +#include "tf/TfInputContextImpl.hpp" + +#include "tf/TfCompositionImpl.hpp" + +namespace IngameIME::tf + +{ + +InputContextImpl::ContextOwner::ContextOwner(InputContextImpl* ctx) + : ctx(ctx) +{ +} + +HRESULT STDMETHODCALLTYPE InputContextImpl::ContextOwner::GetACPFromPoint(const POINT* ptScreen, + DWORD dwFlags, + LONG* pacp) +{ + return TS_E_NOLAYOUT; +} + +HRESULT STDMETHODCALLTYPE InputContextImpl::ContextOwner::GetTextExt(LONG acpStart, + LONG acpEnd, + RECT* prc, + BOOL* pfClipped) +{ + // Fetch bounding box + InternalRect box; + ctx->comp->PreEditRectCallbackHolder::runCallback(box); + *prc = box; + + // Map window coordinate to screen coordinate + MapWindowPoints(ctx->hWnd, NULL, (LPPOINT)prc, 2); + + return S_OK; +} + +HRESULT STDMETHODCALLTYPE InputContextImpl::ContextOwner::GetScreenExt(RECT* prc) +{ + GetWindowRect(ctx->hWnd, prc); + + return S_OK; +} + +HRESULT STDMETHODCALLTYPE InputContextImpl::ContextOwner::GetStatus(TF_STATUS* pdcs) +{ + // Set to 0 indicates the context is editable + pdcs->dwDynamicFlags = 0; + // Set to 0 indicates the context only support single selection + pdcs->dwStaticFlags = 0; + return S_OK; +} + +HRESULT STDMETHODCALLTYPE InputContextImpl::ContextOwner::GetWnd(HWND* phwnd) +{ + *phwnd = ctx->hWnd; + + return S_OK; +} + +HRESULT STDMETHODCALLTYPE InputContextImpl::ContextOwner::GetAttribute(REFGUID rguidAttribute, VARIANT* pvarValue) +{ + pvarValue->vt = VT_EMPTY; + return S_OK; +} + +InputContextImpl::InputContextImpl(const HWND hWnd) + : hWnd(hWnd) +{ + COM_HR_BEGIN(S_OK); + + if (!this->hWnd) THR_HR(E_INVALIDARG); + if (initialCreatorThread() != GetWindowThreadProcessId(hWnd, NULL)) THR_HR(UI_E_WRONG_THREAD); + + CHECK_HR(getThreadMgr(&threadMgr)); + + ComQIPtr threadMgrEx(IID_ITfThreadMgrEx, threadMgr); + CHECK_HR(threadMgrEx->ActivateEx(&clientId, TF_TMAE_UIELEMENTENABLEDONLY)); + + CHECK_HR(threadMgr->CreateDocumentMgr(&emptyDocMgr)); + CHECK_HR(threadMgr->CreateDocumentMgr(&docMgr)); + + comp = std::make_shared(this); + CHECK_HR(docMgr->Push(ctx.get())); + + // Deactivate input contxt + setActivated(false); + + ComQIPtr source(IID_ITfSource, ctx); + owner = new InputContextImpl::ContextOwner(this); + CHECK_HR(source->AdviseSink(IID_ITfContextOwner, owner.get(), &cookie)); + + COM_HR_END(); + COM_HR_THR(); +} + +InputContextImpl::~InputContextImpl() +{ + if (cookie != TF_INVALID_COOKIE) + { + ComQIPtr source(IID_ITfSource, ctx); + source->UnadviseSink(cookie); + cookie = TF_INVALID_COOKIE; + owner.reset(); + } + + if (ctx) + { + setActivated(false); + comp.reset(); + docMgr->Pop(TF_POPF_ALL); + ctx.reset(); + } + + if (docMgr) + { + docMgr.reset(); + emptyDocMgr.reset(); + } + + if (clientId != TF_CLIENTID_NULL) + { + threadMgr->Deactivate(); + threadMgr.reset(); + clientId = TF_CLIENTID_NULL; + } +} + +void InputContextImpl::setActivated(const bool activated) +{ + COM_HR_BEGIN(S_OK); + + CHECK_HR(assertCreatorThread()); + + this->activated = activated; + + ComPtr prevDocumentMgr; + if (activated) + { + CHECK_HR(threadMgr->AssociateFocus(hWnd, docMgr.get(), &prevDocumentMgr)); + } + else + { + // Focus on empty context docMgr can deactivate input method + CHECK_HR(threadMgr->AssociateFocus(hWnd, emptyDocMgr.get(), &prevDocumentMgr)); + // Terminate active composition + comp->terminate(); + } + + COM_HR_END(); + COM_HR_THR(); +} + +bool InputContextImpl::getActivated() const +{ + return activated; +} + +void InputContextImpl::setFullScreen(const bool fullscreen) +{ + this->fullscreen = fullscreen; + if (activated) comp->terminate(); +} + +bool InputContextImpl::getFullScreen() const +{ + return fullscreen; +} + +} // namespace IngameIME::tf diff --git a/src/include/DebugMsg.h b/src/include/DebugMsg.h deleted file mode 100644 index 86f0c8d..0000000 --- a/src/include/DebugMsg.h +++ /dev/null @@ -1,335 +0,0 @@ -#pragma once -#include -#include -#include -#include - -#include - -const std::vector IgnoredMsg = { - WM_MOVE, WM_PAINT, WM_MOUSEACTIVATE, WM_GETMINMAXINFO, WM_WINDOWPOSCHANGING, WM_WINDOWPOSCHANGED, - WM_GETICON, WM_NCACTIVATE, WM_NCLBUTTONDOWN, WM_SYSCOMMAND, WM_LBUTTONDOWN, WM_LBUTTONUP, - WM_CAPTURECHANGED, WM_MOVING, WM_ENTERSIZEMOVE, WM_EXITSIZEMOVE, WM_NCMOUSELEAVE, WM_NCHITTEST, - WM_SETCURSOR, WM_MOUSEFIRST, WM_NCMOUSEMOVE, WM_MOUSELEAVE, WM_KEYDOWN, WM_KEYUP}; - -std::map UsedMsg = {}; - -// These from https://wiki.winehq.org/List_Of_Windows_Messages -const std::map MsgMap = {{0, L"WM_NULL"}, - {1, L"WM_CREATE"}, - {2, L"WM_DESTROY"}, - {3, L"WM_MOVE"}, - {5, L"WM_SIZE"}, - {6, L"WM_ACTIVATE"}, - {7, L"WM_SETFOCUS"}, - {8, L"WM_KILLFOCUS"}, - {10, L"WM_ENABLE"}, - {11, L"WM_SETREDRAW"}, - {12, L"WM_SETTEXT"}, - {13, L"WM_GETTEXT"}, - {14, L"WM_GETTEXTLENGTH"}, - {15, L"WM_PAINT"}, - {16, L"WM_CLOSE"}, - {17, L"WM_QUERYENDSESSION"}, - {18, L"WM_QUIT"}, - {19, L"WM_QUERYOPEN"}, - {20, L"WM_ERASEBKGND"}, - {21, L"WM_SYSCOLORCHANGE"}, - {22, L"WM_ENDSESSION"}, - {24, L"WM_SHOWWINDOW"}, - {25, L"WM_CTLCOLOR"}, - {26, L"WM_WININICHANGE"}, - {27, L"WM_DEVMODECHANGE"}, - {28, L"WM_ACTIVATEAPP"}, - {29, L"WM_FONTCHANGE"}, - {30, L"WM_TIMECHANGE"}, - {31, L"WM_CANCELMODE"}, - {32, L"WM_SETCURSOR"}, - {33, L"WM_MOUSEACTIVATE"}, - {34, L"WM_CHILDACTIVATE"}, - {35, L"WM_QUEUESYNC"}, - {36, L"WM_GETMINMAXINFO"}, - {38, L"WM_PAINTICON"}, - {39, L"WM_ICONERASEBKGND"}, - {40, L"WM_NEXTDLGCTL"}, - {42, L"WM_SPOOLERSTATUS"}, - {43, L"WM_DRAWITEM"}, - {44, L"WM_MEASUREITEM"}, - {45, L"WM_DELETEITEM"}, - {46, L"WM_VKEYTOITEM"}, - {47, L"WM_CHARTOITEM"}, - {48, L"WM_SETFONT"}, - {49, L"WM_GETFONT"}, - {50, L"WM_SETHOTKEY"}, - {51, L"WM_GETHOTKEY"}, - {55, L"WM_QUERYDRAGICON"}, - {57, L"WM_COMPAREITEM"}, - {61, L"WM_GETOBJECT"}, - {65, L"WM_COMPACTING"}, - {68, L"WM_COMMNOTIFY"}, - {70, L"WM_WINDOWPOSCHANGING"}, - {71, L"WM_WINDOWPOSCHANGED"}, - {72, L"WM_POWER"}, - {73, L"WM_COPYGLOBALDATA"}, - {74, L"WM_COPYDATA"}, - {75, L"WM_CANCELJOURNAL"}, - {78, L"WM_NOTIFY"}, - {80, L"WM_INPUTLANGCHANGEREQUEST"}, - {81, L"WM_INPUTLANGCHANGE"}, - {82, L"WM_TCARD"}, - {83, L"WM_HELP"}, - {84, L"WM_USERCHANGED"}, - {85, L"WM_NOTIFYFORMAT"}, - {123, L"WM_CONTEXTMENU"}, - {124, L"WM_STYLECHANGING"}, - {125, L"WM_STYLECHANGED"}, - {126, L"WM_DISPLAYCHANGE"}, - {127, L"WM_GETICON"}, - {128, L"WM_SETICON"}, - {129, L"WM_NCCREATE"}, - {130, L"WM_NCDESTROY"}, - {131, L"WM_NCCALCSIZE"}, - {132, L"WM_NCHITTEST"}, - {133, L"WM_NCPAINT"}, - {134, L"WM_NCACTIVATE"}, - {135, L"WM_GETDLGCODE"}, - {136, L"WM_SYNCPAINT"}, - {160, L"WM_NCMOUSEMOVE"}, - {161, L"WM_NCLBUTTONDOWN"}, - {162, L"WM_NCLBUTTONUP"}, - {163, L"WM_NCLBUTTONDBLCLK"}, - {164, L"WM_NCRBUTTONDOWN"}, - {165, L"WM_NCRBUTTONUP"}, - {166, L"WM_NCRBUTTONDBLCLK"}, - {167, L"WM_NCMBUTTONDOWN"}, - {168, L"WM_NCMBUTTONUP"}, - {169, L"WM_NCMBUTTONDBLCLK"}, - {171, L"WM_NCXBUTTONDOWN"}, - {172, L"WM_NCXBUTTONUP"}, - {173, L"WM_NCXBUTTONDBLCLK"}, - {176, L"EM_GETSEL"}, - {177, L"EM_SETSEL"}, - {178, L"EM_GETRECT"}, - {179, L"EM_SETRECT"}, - {180, L"EM_SETRECTNP"}, - {181, L"EM_SCROLL"}, - {182, L"EM_LINESCROLL"}, - {183, L"EM_SCROLLCARET"}, - {185, L"EM_GETMODIFY"}, - {187, L"EM_SETMODIFY"}, - {188, L"EM_GETLINECOUNT"}, - {189, L"EM_LINEINDEX"}, - {190, L"EM_SETHANDLE"}, - {191, L"EM_GETHANDLE"}, - {192, L"EM_GETTHUMB"}, - {193, L"EM_LINELENGTH"}, - {194, L"EM_REPLACESEL"}, - {195, L"EM_SETFONT"}, - {196, L"EM_GETLINE"}, - {197, L"EM_LIMITTEXT"}, - {197, L"EM_SETLIMITTEXT"}, - {198, L"EM_CANUNDO"}, - {199, L"EM_UNDO"}, - {200, L"EM_FMTLINES"}, - {201, L"EM_LINEFROMCHAR"}, - {202, L"EM_SETWORDBREAK"}, - {203, L"EM_SETTABSTOPS"}, - {204, L"EM_SETPASSWORDCHAR"}, - {205, L"EM_EMPTYUNDOBUFFER"}, - {206, L"EM_GETFIRSTVISIBLELINE"}, - {207, L"EM_SETREADONLY"}, - {209, L"EM_SETWORDBREAKPROC"}, - {209, L"EM_GETWORDBREAKPROC"}, - {210, L"EM_GETPASSWORDCHAR"}, - {211, L"EM_SETMARGINS"}, - {212, L"EM_GETMARGINS"}, - {213, L"EM_GETLIMITTEXT"}, - {214, L"EM_POSFROMCHAR"}, - {215, L"EM_CHARFROMPOS"}, - {216, L"EM_SETIMESTATUS"}, - {217, L"EM_GETIMESTATUS"}, - {224, L"SBM_SETPOS"}, - {225, L"SBM_GETPOS"}, - {226, L"SBM_SETRANGE"}, - {227, L"SBM_GETRANGE"}, - {228, L"SBM_ENABLE_ARROWS"}, - {230, L"SBM_SETRANGEREDRAW"}, - {233, L"SBM_SETSCROLLINFO"}, - {234, L"SBM_GETSCROLLINFO"}, - {235, L"SBM_GETSCROLLBARINFO"}, - {240, L"BM_GETCHECK"}, - {241, L"BM_SETCHECK"}, - {242, L"BM_GETSTATE"}, - {243, L"BM_SETSTATE"}, - {244, L"BM_SETSTYLE"}, - {245, L"BM_CLICK"}, - {246, L"BM_GETIMAGE"}, - {247, L"BM_SETIMAGE"}, - {248, L"BM_SETDONTCLICK"}, - {255, L"WM_INPUT"}, - {256, L"WM_KEYDOWN"}, - {256, L"WM_KEYFIRST"}, - {257, L"WM_KEYUP"}, - {258, L"WM_CHAR"}, - {259, L"WM_DEADCHAR"}, - {260, L"WM_SYSKEYDOWN"}, - {261, L"WM_SYSKEYUP"}, - {262, L"WM_SYSCHAR"}, - {263, L"WM_SYSDEADCHAR"}, - {264, L"WM_KEYLAST"}, - {265, L"WM_UNICHAR"}, - {265, L"WM_WNT_CONVERTREQUESTEX"}, - {266, L"WM_CONVERTREQUEST"}, - {267, L"WM_CONVERTRESULT"}, - {268, L"WM_INTERIM"}, - {269, L"WM_IME_STARTCOMPOSITION"}, - {270, L"WM_IME_ENDCOMPOSITION"}, - {271, L"WM_IME_COMPOSITION"}, - {271, L"WM_IME_KEYLAST"}, - {272, L"WM_INITDIALOG"}, - {273, L"WM_COMMAND"}, - {274, L"WM_SYSCOMMAND"}, - {275, L"WM_TIMER"}, - {276, L"WM_HSCROLL"}, - {277, L"WM_VSCROLL"}, - {278, L"WM_INITMENU"}, - {279, L"WM_INITMENUPOPUP"}, - {280, L"WM_SYSTIMER"}, - {287, L"WM_MENUSELECT"}, - {288, L"WM_MENUCHAR"}, - {289, L"WM_ENTERIDLE"}, - {290, L"WM_MENURBUTTONUP"}, - {291, L"WM_MENUDRAG"}, - {292, L"WM_MENUGETOBJECT"}, - {293, L"WM_UNINITMENUPOPUP"}, - {294, L"WM_MENUCOMMAND"}, - {295, L"WM_CHANGEUISTATE"}, - {296, L"WM_UPDATEUISTATE"}, - {297, L"WM_QUERYUISTATE"}, - {306, L"WM_CTLCOLORMSGBOX"}, - {307, L"WM_CTLCOLOREDIT"}, - {308, L"WM_CTLCOLORLISTBOX"}, - {309, L"WM_CTLCOLORBTN"}, - {310, L"WM_CTLCOLORDLG"}, - {311, L"WM_CTLCOLORSCROLLBAR"}, - {312, L"WM_CTLCOLORSTATIC"}, - {512, L"WM_MOUSEFIRST"}, - {512, L"WM_MOUSEMOVE"}, - {513, L"WM_LBUTTONDOWN"}, - {514, L"WM_LBUTTONUP"}, - {515, L"WM_LBUTTONDBLCLK"}, - {516, L"WM_RBUTTONDOWN"}, - {517, L"WM_RBUTTONUP"}, - {518, L"WM_RBUTTONDBLCLK"}, - {519, L"WM_MBUTTONDOWN"}, - {520, L"WM_MBUTTONUP"}, - {521, L"WM_MBUTTONDBLCLK"}, - {521, L"WM_MOUSELAST"}, - {522, L"WM_MOUSEWHEEL"}, - {523, L"WM_XBUTTONDOWN"}, - {524, L"WM_XBUTTONUP"}, - {525, L"WM_XBUTTONDBLCLK"}, - {528, L"WM_PARENTNOTIFY"}, - {529, L"WM_ENTERMENULOOP"}, - {530, L"WM_EXITMENULOOP"}, - {531, L"WM_NEXTMENU"}, - {532, L"WM_SIZING"}, - {533, L"WM_CAPTURECHANGED"}, - {534, L"WM_MOVING"}, - {536, L"WM_POWERBROADCAST"}, - {537, L"WM_DEVICECHANGE"}, - {544, L"WM_MDICREATE"}, - {545, L"WM_MDIDESTROY"}, - {546, L"WM_MDIACTIVATE"}, - {547, L"WM_MDIRESTORE"}, - {548, L"WM_MDINEXT"}, - {549, L"WM_MDIMAXIMIZE"}, - {550, L"WM_MDITILE"}, - {551, L"WM_MDICASCADE"}, - {552, L"WM_MDIICONARRANGE"}, - {553, L"WM_MDIGETACTIVE"}, - {560, L"WM_MDISETMENU"}, - {561, L"WM_ENTERSIZEMOVE"}, - {562, L"WM_EXITSIZEMOVE"}, - {563, L"WM_DROPFILES"}, - {564, L"WM_MDIREFRESHMENU"}, - {640, L"WM_IME_REPORT"}, - {641, L"WM_IME_SETCONTEXT"}, - {642, L"WM_IME_NOTIFY"}, - {643, L"WM_IME_CONTROL"}, - {644, L"WM_IME_COMPOSITIONFULL"}, - {645, L"WM_IME_SELECT"}, - {646, L"WM_IME_CHAR"}, - {648, L"WM_IME_REQUEST"}, - {656, L"WM_IMEKEYDOWN"}, - {656, L"WM_IME_KEYDOWN"}, - {657, L"WM_IMEKEYUP"}, - {657, L"WM_IME_KEYUP"}, - {672, L"WM_NCMOUSEHOVER"}, - {673, L"WM_MOUSEHOVER"}, - {674, L"WM_NCMOUSELEAVE"}, - {675, L"WM_MOUSELEAVE"}, - {768, L"WM_CUT"}, - {769, L"WM_COPY"}, - {770, L"WM_PASTE"}, - {771, L"WM_CLEAR"}, - {772, L"WM_UNDO"}, - {773, L"WM_RENDERFORMAT"}, - {774, L"WM_RENDERALLFORMATS"}, - {775, L"WM_DESTROYCLIPBOARD"}, - {776, L"WM_DRAWCLIPBOARD"}, - {777, L"WM_PAINTCLIPBOARD"}, - {778, L"WM_VSCROLLCLIPBOARD"}, - {779, L"WM_SIZECLIPBOARD"}, - {780, L"WM_ASKCBFORMATNAME"}, - {781, L"WM_CHANGECBCHAIN"}, - {782, L"WM_HSCROLLCLIPBOARD"}, - {783, L"WM_QUERYNEWPALETTE"}, - {784, L"WM_PALETTEISCHANGING"}, - {785, L"WM_PALETTECHANGED"}, - {786, L"WM_HOTKEY"}, - {791, L"WM_PRINT"}, - {792, L"WM_PRINTCLIENT"}, - {793, L"WM_APPCOMMAND"}, - {856, L"WM_HANDHELDFIRST"}, - {863, L"WM_HANDHELDLAST"}, - {864, L"WM_AFXFIRST"}, - {895, L"WM_AFXLAST"}, - {896, L"WM_PENWINFIRST"}, - {897, L"WM_RCRESULT"}, - {898, L"WM_HOOKRCRESULT"}, - {899, L"WM_GLOBALRCCHANGE"}, - {899, L"WM_PENMISCINFO"}, - {900, L"WM_SKB"}, - {901, L"WM_HEDITCTL"}, - {901, L"WM_PENCTL"}, - {902, L"WM_PENMISC"}, - {903, L"WM_CTLINIT"}, - {904, L"WM_PENEVENT"}, - {911, L"WM_PENWINLAST"}, - {1024, L"WM_USER"}}; - -std::wstring GetMessageText(UINT msg) noexcept -{ - UsedMsg[msg]++; - - auto iter = MsgMap.find(msg); - if (iter == MsgMap.end()) - return L""; - else - return (*iter).second; -} - -void ShowUsedMsg() noexcept -{ - for (auto&& kv : UsedMsg) { wprintf(L"[%04x](%s): %zu\n", kv.first, GetMessageText(kv.first).c_str(), kv.second); } -} - -void ShowMessageText(UINT msg) noexcept -{ - // Dont show Ignored msgs - if (std::find(IgnoredMsg.begin(), IgnoredMsg.end(), msg) == IgnoredMsg.end()) { - auto text = GetMessageText(msg); - if (!text.empty()) wprintf(L"WndProc: [%04x](%s)\n", msg, text.c_str()); - } -} \ No newline at end of file diff --git a/src/include/common/InputContextImpl.hpp b/src/include/common/InputContextImpl.hpp deleted file mode 100644 index fd20313..0000000 --- a/src/include/common/InputContextImpl.hpp +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once -#include - -#include "InputContext.hpp" - -namespace IngameIME { - struct InternalRect : public IngameIME::PreEditRect - { - InternalRect() = default; - - operator RECT() noexcept - { - RECT rect; - rect.left = this->left; - rect.top = this->top; - rect.right = this->right; - rect.bottom = this->bottom; - - return rect; - } - }; -}// namespace IngameIME \ No newline at end of file diff --git a/src/include/common/InputProcessorImpl.hpp b/src/include/common/InputProcessorImpl.hpp deleted file mode 100644 index fc8d63a..0000000 --- a/src/include/common/InputProcessorImpl.hpp +++ /dev/null @@ -1,339 +0,0 @@ -#pragma once -#include -#include -#include - -#include -#pragma comment(lib, "imm32.lib") - -#include -#pragma comment(lib, "shlwapi.lib") - -#include - -#include "InputProcessor.hpp" - -#include "ComBSTR.hpp" -#include "ComObjectBase.hpp" -#include "ComPtr.hpp" -#include "FormatUtil.hpp" -#include "TfFunction.hpp" - -namespace IngameIME { - struct InternalLocale : public IngameIME::Locale - { - protected: - static std::map> weakRefs; - - protected: - const LANGID langId; - - protected: - static std::wstring getLocaleString(const LANGID langid) - { - LCID lcid = MAKELCID(langid, SORT_DEFAULT); - - int size = GetLocaleInfoW(lcid, LOCALE_SNAME, NULL, 0); - auto buf = std::make_unique(size); - - GetLocaleInfoW(lcid, LOCALE_SNAME, buf.get(), size); - - return std::wstring(buf.get(), size); - } - - static std::wstring getLocaleName(const LANGID langid) - { - LCID lcid = MAKELCID(langid, SORT_DEFAULT); - - int size = GetLocaleInfoW(lcid, LOCALE_SLOCALIZEDDISPLAYNAME, NULL, 0); - auto buf = std::make_unique(size); - - GetLocaleInfoW(lcid, LOCALE_SLOCALIZEDDISPLAYNAME, buf.get(), size); - - return std::wstring(buf.get(), size); - } - - public: - InternalLocale(const LANGID langId) noexcept : langId(langId) - { - locale = getLocaleString(langId); - name = getLocaleName(langId); - } - - ~InternalLocale() noexcept - { - weakRefs.erase(langId); - } - - public: - static std::shared_ptr getLocale(const LANGID langId) - { - auto iter = weakRefs.find(langId); - - std::shared_ptr locale; - - // Create new locale if not exist or expired - if (iter == weakRefs.end() || !(locale = (*iter).second.lock())) { - locale = std::make_shared(langId); - weakRefs[langId] = locale; - } - - return locale; - } - }; - std::map> InternalLocale::weakRefs = {}; - - class InputProcessorImpl : public IngameIME::InputProcessor { - protected: - struct CompareProfile - { - bool operator()(const TF_INPUTPROCESSORPROFILE& s1, const TF_INPUTPROCESSORPROFILE& s2) const - { - if (s1.dwProfileType == s2.dwProfileType) - if (s1.dwProfileType == TF_PROFILETYPE_KEYBOARDLAYOUT) - return s1.hkl < s2.hkl; - else - return memcmp(&s1.clsid, &s2.clsid, sizeof(CLSID)) < 0; - else - return s1.dwProfileType < s2.dwProfileType; - } - }; - - static std::map, CompareProfile> weakRefs; - - protected: - static std::wstring getKeyboardLayoutName(LANGID langId) - { - auto result = format(L"[KL: 0x%1!08x!]", langId); - - HKEY layouts; - if (ERROR_SUCCESS == RegOpenKeyExW(HKEY_LOCAL_MACHINE, - L"SYSTEM\\CurrentControlSet\\Control\\Keyboard Layouts", - 0, - KEY_READ, - &layouts)) { - // the key of the keyboard layout is its langid - char layoutKey[9]; - snprintf(layoutKey, 9, "%08x", langId); - - HKEY layout; - if (ERROR_SUCCESS == RegOpenKeyExA(layouts, layoutKey, 0, KEY_READ, &layout)) { - // Get data size first - DWORD size; - if (ERROR_SUCCESS == RegGetValueW(layout, - NULL, - L"Layout Display Name", - RRF_RT_REG_SZ | RRF_RT_REG_EXPAND_SZ | RRF_NOEXPAND, - NULL, - NULL, - &size)) { - // Get resource key of the name - auto resKey = std::make_unique(size); - if (ERROR_SUCCESS == RegGetValueW(layout, - NULL, - L"Layout Display Name", - RRF_RT_REG_SZ | RRF_RT_REG_EXPAND_SZ | RRF_NOEXPAND, - NULL, - resKey.get(), - &size)) { - // Get the layout name by resource key - wchar_t layoutName[64]; - HRESULT hr; - if (SUCCEEDED(hr = SHLoadIndirectString(resKey.get(), layoutName, 64, NULL))) { - result = std::wstring(layoutName); - } - } - } - RegCloseKey(layout); - } - RegCloseKey(layouts); - } - return result; - } - - static std::wstring getTextServiceName(const TF_INPUTPROCESSORPROFILE profile) - { - auto result = format(L"[TIP: {%1!08x!-0x%1!04x!-0x%1!04x!-0x%1!04x!-0x%1!012x!}]", - profile.clsid.Data1, - profile.clsid.Data2, - profile.clsid.Data3, - profile.clsid.Data4); - - COM_HR_BEGIN(S_OK); - - libtf::ComPtr inputProcessorProfiles; - CHECK_HR(libtf::createInputProcessorProfiles(&inputProcessorProfiles)); - - libtf::ComBSTR name; - CHECK_HR(inputProcessorProfiles->GetLanguageProfileDescription( - profile.clsid, profile.langid, profile.guidProfile, &name)); - - result = std::wstring(name.bstr); - - COM_HR_END(); - - return result; - } - - static std::wstring getImmName(HKL hkl) - { - auto size = ImmGetDescriptionW(hkl, NULL, 0) + 1; - - if (size == 0) return format(L"[IMM: %1!08x!]", hkl); - - auto buf = std::make_unique(size); - ImmGetDescriptionW(hkl, buf.get(), size); - - return std::wstring(buf.get(), size); - } - - /** - * @brief Check if the HKL correspond to a Imm InputMethod - * - * @param hkl hkl to check - * @return true - Imm InputMethod - * @return false - Normal KeyboardLayout - */ - static bool isImm(HKL hkl) - { - return (0xF000 & HIWORD(hkl)) == 0xE000; - } - - static std::wstring getInputProcessorName(const TF_INPUTPROCESSORPROFILE profile) - { - switch (profile.dwProfileType) { - case TF_PROFILETYPE_INPUTPROCESSOR: return getTextServiceName(profile); - case TF_PROFILETYPE_KEYBOARDLAYOUT: - if (isImm(profile.hkl)) - return getImmName(profile.hkl); - else - return getKeyboardLayoutName(profile.langid); - default: return L"[InputProcessor: unknown]"; - } - } - - protected: - const TF_INPUTPROCESSORPROFILE profile; - - public: - /** - * @brief If this is a Japanese InputProcessor - * - */ - bool isJap; - - public: - InputProcessorImpl(const TF_INPUTPROCESSORPROFILE profile) : profile(profile) - { - type = profile.dwProfileType == TF_PROFILETYPE_INPUTPROCESSOR || isImm(profile.hkl) ? - IngameIME::InputProcessorType::TextService : - IngameIME::InputProcessorType::KeyboardLayout; - name = getInputProcessorName(profile); - locale = IngameIME::InternalLocale::getLocale(profile.langid); - isJap = locale->locale.compare(0, 2, L"ja") == 0; - } - - ~InputProcessorImpl() - { - weakRefs.erase(profile); - } - - public: - static std::shared_ptr getInputProcessor(const TF_INPUTPROCESSORPROFILE profile) - { - auto iter = weakRefs.find(profile); - - std::shared_ptr proc; - - // Create new proc if not exist or expired - if (iter == weakRefs.end() || !(proc = (*iter).second.lock())) { - proc = std::make_shared(profile); - weakRefs[profile] = proc; - } - - return proc; - } - - static std::shared_ptr getActiveInputProcessor() - { - COM_HR_BEGIN(S_OK); - - if (!IsGUIThread(false)) THR_HR(UI_E_WRONG_THREAD); - - libtf::ComPtr inputProcessorProfiles; - CHECK_HR(libtf::createInputProcessorProfiles(&inputProcessorProfiles)); - libtf::ComQIPtr inputProcessorMgr(IID_ITfInputProcessorProfileMgr, - inputProcessorProfiles); - - TF_INPUTPROCESSORPROFILE profile; - CHECK_HR(inputProcessorMgr->GetActiveProfile(GUID_TFCAT_TIP_KEYBOARD, &profile)); - return InputProcessorImpl::getInputProcessor(profile); - - COM_HR_END(); - COM_HR_THR(); - - // Should not reach here - return nullptr; - } - - static std::list> getInputProcessors() - { - std::list> result; - - COM_HR_BEGIN(S_OK); - - if (!IsGUIThread(false)) break; - - libtf::ComPtr profiles; - CHECK_HR(libtf::createInputProcessorProfiles(&profiles)); - libtf::ComQIPtr procMgr(IID_ITfInputProcessorProfileMgr, profiles); - - libtf::ComPtr enumProfiles; - // Pass 0 to langid to enum all profiles - CHECK_HR(procMgr->EnumProfiles(0, &enumProfiles)); - - TF_INPUTPROCESSORPROFILE profile[1]; - while (true) { - ULONG fetch; - CHECK_HR(enumProfiles->Next(1, profile, &fetch)); - - // Reach end - if (fetch == 0) break; - - // InputProcessor not enabled can't be activated - if (!(profile[0].dwFlags & TF_IPP_FLAG_ENABLED)) continue; - - result.push_back(InputProcessorImpl::getInputProcessor(profile[0])); - } - - COM_HR_END(); - - return result; - } - - public: - virtual void setActivated() const override - { - COM_HR_BEGIN(S_OK); - - if (!IsGUIThread(false)) THR_HR(UI_E_WRONG_THREAD); - - libtf::ComPtr inputProcessorProfiles; - CHECK_HR(libtf::createInputProcessorProfiles(&inputProcessorProfiles)); - libtf::ComQIPtr inputProcessorMgr(IID_ITfInputProcessorProfileMgr, - inputProcessorProfiles); - - CHECK_HR(inputProcessorMgr->ActivateProfile(profile.dwProfileType, - profile.langid, - profile.clsid, - profile.guidProfile, - profile.hkl, - TF_IPPMF_DONTCARECURRENTINPUTLANGUAGE)); - - COM_HR_END(); - COM_HR_THR(); - } - }; - std::map, InputProcessorImpl::CompareProfile> - InputProcessorImpl::weakRefs = {}; -}// namespace IngameIME \ No newline at end of file diff --git a/src/include/imm/ImmCompositionImpl.hpp b/src/include/imm/ImmCompositionImpl.hpp deleted file mode 100644 index eb03d50..0000000 --- a/src/include/imm/ImmCompositionImpl.hpp +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once -#include "ImmInputContextImpl.hpp" - -namespace libimm { - class CompositionImpl : public IngameIME::Composition { - protected: - InputContextImpl* inputCtx; - - public: - CompositionImpl(InputContextImpl* inputCtx) : inputCtx(inputCtx) {} - - public: - /** - * @brief Terminate active composition - * - */ - virtual void terminate() noexcept override - { - ImmNotifyIME(inputCtx->ctx, NI_COMPOSITIONSTR, CPS_COMPLETE, 0); - } - }; -}// namespace libimm diff --git a/src/include/imm/ImmIngameIMEImpl.hpp b/src/include/imm/ImmIngameIMEImpl.hpp deleted file mode 100644 index bf08134..0000000 --- a/src/include/imm/ImmIngameIMEImpl.hpp +++ /dev/null @@ -1,48 +0,0 @@ -#pragma once -#include "IngameIME.hpp" -#include "InputProcessorImpl.hpp" - -#include "ImmInputContextImpl.hpp" - -namespace libimm { - class GlobalImpl : public IngameIME::Global { - public: - /** - * @brief Get Active InputProcessor - * - * @return std::shared_ptr - */ - virtual std::shared_ptr getActiveInputProcessor() const override - { - return IngameIME::InputProcessorImpl::getActiveInputProcessor(); - } - - /** - * @brief Get system availiable InputProcessor - * - * @return std::list> - */ - virtual std::list> getInputProcessors() const override - { - return IngameIME::InputProcessorImpl::getInputProcessors(); - } - - /** - * @brief Get the InputContext object - * - * @return std::shared_ptr - */ - virtual std::shared_ptr getInputContext(void* hWnd, ...) override - { - std::shared_ptr ctx; - - auto iter = InputContextImpl::InputCtxMap.find(reinterpret_cast(hWnd)); - if (iter == InputContextImpl::InputCtxMap.end() || !(ctx = (*iter).second.lock())) { - ctx = std::make_shared(reinterpret_cast(hWnd)); - - InputContextImpl::InputCtxMap[reinterpret_cast(hWnd)] = ctx; - } - return ctx; - } - }; -}// namespace libimm \ No newline at end of file diff --git a/src/include/imm/ImmInputContextImpl.hpp b/src/include/imm/ImmInputContextImpl.hpp deleted file mode 100644 index 5475898..0000000 --- a/src/include/imm/ImmInputContextImpl.hpp +++ /dev/null @@ -1,293 +0,0 @@ -#pragma once -#include - -#include -#pragma comment(lib, "imm32.lib") - -#include "IngameIME.hpp" -#include "InputContextImpl.hpp" -#include "InputProcessorImpl.hpp" - -namespace libimm { - class InputContextImpl : public IngameIME::InputContext { - protected: - static std::map> InputCtxMap; - static LRESULT WndProc(HWND hWnd, UINT msg, WPARAM wparam, LPARAM lparam) - { - auto iter = InputCtxMap.find(hWnd); - - std::shared_ptr inputCtx; - if (iter != InputCtxMap.end() && (inputCtx = (*iter).second.lock())) { - switch (msg) { - case WM_INPUTLANGCHANGE: - IngameIME::Global::getInstance().runCallback(IngameIME::InputProcessorState::FullUpdate, - inputCtx->getInputProcCtx()); - break; - case WM_IME_SETCONTEXT: - // We should always hide Composition Window to make the PreEditCallback for work - lparam &= ~ISC_SHOWUICOMPOSITIONWINDOW; - - if (inputCtx->fullscreen) { - // Hide Candidate Window - lparam &= ~ISC_SHOWUICANDIDATEWINDOW; - } - break; - case WM_IME_STARTCOMPOSITION: - inputCtx->comp->IngameIME::PreEditCallbackHolder::runCallback( - IngameIME::CompositionState::Begin, nullptr); - return true; - case WM_IME_COMPOSITION: - if (lparam & (GCS_COMPSTR | GCS_CURSORPOS)) inputCtx->procPreEdit(); - if (lparam & GCS_RESULTSTR) inputCtx->procCommit(); - - if (!inputCtx->fullscreen) inputCtx->procPreEditRect(); - - // when lparam == 0 that means current Composition has been canceled - if (lparam) return true; - case WM_IME_ENDCOMPOSITION: - inputCtx->comp->IngameIME::PreEditCallbackHolder::runCallback(IngameIME::CompositionState::End, - nullptr); - inputCtx->comp->IngameIME::CandidateListCallbackHolder::runCallback( - IngameIME::CandidateListState::End, nullptr); - return true; - case WM_IME_NOTIFY: - if (inputCtx->fullscreen) switch (wparam) { - case IMN_OPENCANDIDATE: - inputCtx->comp->IngameIME::CandidateListCallbackHolder::runCallback( - IngameIME::CandidateListState::Begin, nullptr); - return true; - case IMN_CHANGECANDIDATE: inputCtx->procCand(); return true; - case IMN_CLOSECANDIDATE: - inputCtx->comp->IngameIME::CandidateListCallbackHolder::runCallback( - IngameIME::CandidateListState::End, nullptr); - return true; - default: break; - } - if (wparam == IMN_SETCONVERSIONMODE) { - IngameIME::Global::getInstance().runCallback( - IngameIME::InputProcessorState::InputModeUpdate, inputCtx->getInputProcCtx()); - } - break; - case WM_IME_CHAR: - // Commit text already handled at WM_IME_COMPOSITION - return true; - default: return CallWindowProcW(inputCtx->prevProc, hWnd, msg, wparam, lparam); - } - } - return DefWindowProcW(hWnd, msg, wparam, lparam); - } - - protected: - HWND hWnd; - WNDPROC prevProc; - - HIMC ctx; - - bool activated{false}; - bool fullscreen{false}; - - friend class CompositionImpl; - friend class GlobalImpl; - - public: - InputContextImpl(HWND hWnd); - ~InputContextImpl() - { - InputCtxMap.erase(hWnd); - comp->terminate(); - ImmAssociateContextEx(hWnd, NULL, IACE_DEFAULT); - SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)prevProc); - } - - protected: - /** - * @brief Retrive PreEdit info for current Composition - * - */ - void procPreEdit() - { - // PreEdit Text - auto size = ImmGetCompositionStringW(ctx, GCS_COMPSTR, NULL, 0); - // Error occurs - if (size <= 0) return; - - auto buf = std::make_unique(size / sizeof(WCHAR)); - ImmGetCompositionStringW(ctx, GCS_COMPSTR, buf.get(), size); - - // Selection - int sel = ImmGetCompositionStringW(ctx, GCS_CURSORPOS, NULL, 0); - - IngameIME::PreEditContext ctx; - ctx.content = std::wstring(buf.get(), size / sizeof(WCHAR)); - ctx.selStart = ctx.selEnd = sel; - - comp->IngameIME::PreEditCallbackHolder::runCallback(IngameIME::CompositionState::Update, &ctx); - } - - /** - * @brief Retrive Commit text for current Composition - * - */ - void procCommit() - { - auto size = ImmGetCompositionStringW(ctx, GCS_RESULTSTR, NULL, 0); - // Error occurs - if (size <= 0) return; - - auto buf = std::make_unique(size / sizeof(WCHAR)); - ImmGetCompositionStringW(ctx, GCS_RESULTSTR, buf.get(), size); - - comp->IngameIME::CommitCallbackHolder::runCallback(std::wstring(buf.get(), size / sizeof(WCHAR))); - } - - /** - * @brief Set CandidateList window's position for current Composition - * - */ - void procPreEditRect() - { - IngameIME::InternalRect rect; - comp->IngameIME::PreEditRectCallbackHolder::runCallback(rect); - - CANDIDATEFORM cand; - cand.dwIndex = 0; - cand.dwStyle = CFS_EXCLUDE; - cand.ptCurrentPos.x = rect.left; - cand.ptCurrentPos.y = rect.top; - cand.rcArea = rect; - ImmSetCandidateWindow(ctx, &cand); - - COMPOSITIONFORM comp; - comp.dwStyle = CFS_RECT; - comp.ptCurrentPos.x = rect.left; - comp.ptCurrentPos.y = rect.top; - comp.rcArea = rect; - ImmSetCompositionWindow(ctx, &comp); - } - - void procCand() - { - IngameIME::CandidateListContext candCtx; - - auto size = ImmGetCandidateListW(ctx, 0, NULL, 0); - // Error occurs - if (size == 0) return; - - auto buf = std::make_unique(size); - auto cand = (LPCANDIDATELIST)buf.get(); - - ImmGetCandidateListW(ctx, 0, cand, size); - - auto pageSize = cand->dwPageSize; - auto candCount = cand->dwCount; - - auto pageStart = cand->dwPageStart; - auto pageEnd = pageStart + pageSize; - candCtx.selection = cand->dwSelection; - // Absolute index to relative index - candCtx.selection -= pageStart; - - for (size_t i = 0; i < pageSize; i++) { - auto strStart = buf.get() + cand->dwOffset[i + pageStart]; - auto strEnd = - buf.get() + (((i + pageStart + 1) < candCount) ? cand->dwOffset[i + pageStart + 1] : size); - auto len = (strEnd - strStart) / sizeof(WCHAR); - - candCtx.candidates.push_back(std::wstring((wchar_t*)strStart, len)); - } - - comp->IngameIME::CandidateListCallbackHolder::runCallback(IngameIME::CandidateListState::Update, &candCtx); - } - - public: - IngameIME::InputProcessorContext getInputProcCtx() - { - IngameIME::InputProcessorContext result; - - DWORD mode; - ImmGetConversionStatus(ctx, &mode, NULL); - - auto activeProc = IngameIME::InputProcessorImpl::getActiveInputProcessor(); - - std::list modes; - if (activeProc->type == IngameIME::InputProcessorType::KeyboardLayout) - modes.push_back(L"AlphaNumeric"); - else { - if (mode & IME_CMODE_NATIVE) { - modes.push_back(L"Native"); - - if (activeProc->isJap) - if (mode & IME_CMODE_KATAKANA) - modes.push_back(L"Katakana"); - else - modes.push_back(L"Hiragana"); - } - else - modes.push_back(L"AlphaNumeric"); - - if (mode & IME_CMODE_FULLSHAPE) - modes.push_back(L"FullShape"); - else - modes.push_back(L"HalfShape"); - } - - result.proc = activeProc; - result.modes = modes; - - return result; - } - - public: - /** - * @brief Set InputContext activate state - * - * @param activated if InputContext activated - */ - virtual void setActivated(const bool activated) noexcept override - { - this->activated = activated; - - if (activated) - ImmAssociateContext(hWnd, ctx); - else - ImmAssociateContext(hWnd, NULL); - } - /** - * @brief Get if InputContext activated - * - * @return true activated - * @return false not activated - */ - virtual bool getActivated() const noexcept override - { - return activated; - } - /** - * @brief Set InputContext full screen state - * - * @param fullscreen if InputContext full screen - */ - virtual void setFullScreen(const bool fullscreen) noexcept override - { - this->fullscreen = fullscreen; - - if (activated) { - comp->terminate(); - // Refresh InputContext - ImmAssociateContext(hWnd, NULL); - ImmAssociateContext(hWnd, ctx); - } - } - /** - * @brief Get if InputContext in full screen state - * - * @return true full screen mode - * @return false window mode - */ - virtual bool getFullScreen() const noexcept override - { - return fullscreen; - } - }; - std::map> InputContextImpl::InputCtxMap = {}; -}// namespace libimm \ No newline at end of file diff --git a/src/include/tf/ComBSTR.hpp b/src/include/tf/ComBSTR.hpp deleted file mode 100644 index 0d50f45..0000000 --- a/src/include/tf/ComBSTR.hpp +++ /dev/null @@ -1,39 +0,0 @@ -#pragma once -#include - -#include - -namespace libtf { - class ComBSTR { - public: - BSTR bstr{nullptr}; - - public: - constexpr ComBSTR() = default; - constexpr ComBSTR(std::nullptr_t) noexcept {} - - ~ComBSTR() - { - SysFreeString(bstr); - } - - /** - * @brief Acquire address of bstr - * - * this usually use for acquiring BSTR string, so the bstr must be null, - * if you do want to acquire the address of the bstr, use &bstr instead - * - * @return address of the bstr - */ - [[nodiscard]] BSTR* operator&() - { - if (bstr) throw new std::runtime_error("Acquire address for non-null pointer"); - return &bstr; - } - - explicit operator bool() const noexcept - { - return bstr != nullptr; - } - }; -}// namespace libtf \ No newline at end of file diff --git a/src/include/tf/ComException.hpp b/src/include/tf/ComException.hpp deleted file mode 100644 index 132f639..0000000 --- a/src/include/tf/ComException.hpp +++ /dev/null @@ -1,16 +0,0 @@ -#pragma once -#include - -#include "FormatUtil.hpp" - -namespace libtf { - - class ComException : public std::runtime_error { - public: - const HRESULT hr; - ComException(HRESULT hr) - : hr(hr), std::runtime_error(format("ComException[0x%1!08x!]: %2!s!", hr, format(hr).c_str()).c_str()) - { - } - }; -}// namespace libtf diff --git a/src/include/tf/ComObjectBase.hpp b/src/include/tf/ComObjectBase.hpp deleted file mode 100644 index 36724ae..0000000 --- a/src/include/tf/ComObjectBase.hpp +++ /dev/null @@ -1,69 +0,0 @@ -#pragma once -#include - -#include "ComException.hpp" - -#define COM_DEF_BEGIN() \ - virtual HRESULT STDMETHODCALLTYPE QueryInterface( \ - /* [in] */ REFIID riid, /* [iid_is][out] */ _COM_Outptr_ void __RPC_FAR* __RPC_FAR* ppvObject) override \ - { \ - if (!ppvObject) return E_POINTER; \ - *ppvObject = nullptr; \ - if (IsEqualIID(IID_IUnknown, riid)) *ppvObject = this; -#define COM_DEF_INF(InfName) \ - if (IsEqualIID(IID_##InfName, riid)) *ppvObject = static_cast(this); -#define COM_DEF_END() \ - if (*ppvObject) { \ - this->AddRef(); \ - return S_OK; \ - } \ - return E_NOINTERFACE; \ - } \ - virtual ULONG STDMETHODCALLTYPE AddRef(void) override \ - { \ - return ComObjectBase::InternalAddRef(); \ - } \ - virtual ULONG STDMETHODCALLTYPE Release(void) override \ - { \ - return ComObjectBase::InternalRelease(); \ - } - -#define COM_HR_BEGIN(DefaultHR) \ - HRESULT hr = DefaultHR; \ - do { -#define COM_HR_END() \ - } \ - while (0) \ - ; -#define THR_HR(hr) throw libtf::ComException(hr); -#define CHECK_HR(exp) \ - if (FAILED(hr = (exp))) break; -#define COM_HR_THR() \ - if (FAILED(hr)) THR_HR(hr); -#define COM_HR_RET() return hr; - -namespace libtf { - class ComObjectBase { - private: - /** - * @brief Reference count of the ComObj - * - */ - DWORD ref = 0; - - public: - virtual ~ComObjectBase() = default; - - protected: - ULONG InternalAddRef(void) - { - return ++ref; - } - - ULONG InternalRelease(void) - { - if (--ref == 0) delete this; - return ref; - } - }; -}// namespace libtf \ No newline at end of file diff --git a/src/include/tf/ComPtr.hpp b/src/include/tf/ComPtr.hpp deleted file mode 100644 index 303277c..0000000 --- a/src/include/tf/ComPtr.hpp +++ /dev/null @@ -1,148 +0,0 @@ -#pragma once -#include - -namespace libtf { - template - class ComPtr { - protected: - T* ptr{nullptr}; - - public: - constexpr ComPtr() = default; - constexpr ComPtr(std::nullptr_t) noexcept {} - - explicit ComPtr(T* ptr) noexcept : ptr(ptr) - { - if (ptr) ptr->AddRef(); - } - - ComPtr(const ComPtr& other) noexcept - { - ptr = other.ptr; - if (ptr) ptr->AddRef(); - } - - ComPtr(const ComPtr&& other) noexcept - { - ptr = other.ptr; - other.ptr = nullptr; - } - - ~ComPtr() noexcept - { - if (ptr) ptr->Release(); - } - - public: - [[nodiscard]] T* get() const noexcept - { - return ptr; - } - - void swap(ComPtr& other) noexcept - { - std::swap(ptr, other.ptr); - } - - void reset() noexcept - { - ComPtr().swap(*this); - } - - void reset(T* ptr) noexcept - { - ComPtr(ptr).swap(*this); - } - - /** - * @brief Attach to excting interface without AddRef() - * - * @param ptr pointer to interface - */ - void attach(T* ptr) noexcept - { - reset(); - this->ptr = ptr; - } - - /** - * @brief Detach the interface without Release() - * - * @return pointer to the detached interface - */ - T* detach() noexcept - { - T* p = ptr; - ptr = nullptr; - return p; - } - - ComPtr& operator=(const ComPtr& right) noexcept - { - ComPtr(right).swap(*this); - return *this; - } - - ComPtr& operator=(T* ptr) noexcept - { - ComPtr(ptr).swap(*this); - return *this; - } - - [[nodiscard]] T& operator*() const noexcept - { - return *ptr; - } - - [[nodiscard]] T* operator->() const noexcept - { - return ptr; - } - - /** - * @brief Acquire address of raw pointer - * - * this usually use for acquiring the interface, so the pointer must be null, - * if you do want to acquire the address of the raw pointer, use &get() instead - * - * @return address of the raw pointer - */ - [[nodiscard]] T** operator&() - { - if (ptr) throw new std::runtime_error("Acquire address for non-null pointer"); - return &ptr; - } - - explicit operator bool() const noexcept - { - return ptr != nullptr; - } - }; - - template - class ComQIPtr : public ComPtr { - protected: - const IID iid; - - public: - constexpr ComQIPtr(const IID iid) noexcept : iid(iid) {} - constexpr ComQIPtr(const IID iid, std::nullptr_t) noexcept : iid(iid) {} - - template - ComQIPtr(const IID iid, const ComPtr& other) noexcept : iid(iid) - { - IUnknown* p; - if (!other || FAILED(other->QueryInterface(iid, (void**)&p))) { this->ptr = nullptr; } - else { - this->ptr = reinterpret_cast(p); - } - } - - template - ComQIPtr& operator=(const ComPtr& other) noexcept - { - ComQIPtr(iid, other).swap(*this); - return *this; - } - }; -}// namespace libtf \ No newline at end of file diff --git a/src/include/tf/IThreadAssociate.hpp b/src/include/tf/IThreadAssociate.hpp deleted file mode 100644 index a037971..0000000 --- a/src/include/tf/IThreadAssociate.hpp +++ /dev/null @@ -1,44 +0,0 @@ -#pragma once -#include -#include - -namespace libtf { - class IThreadAssociate { - private: - DWORD threadId; - - protected: - /** - * @brief Initialize the creator thread - * - * @param threadId creator thread id, set to null to use current thread - * @return DWORD creator thread id - */ - DWORD initialCreatorThread(const DWORD threadId = 0) - { - return this->threadId = (threadId != 0 ? threadId : GetCurrentThreadId()); - } - - public: - /** - * @brief Assert the calling thread is the thread that create this object - * - * @return UI_E_WRONG_THREAD if the calling thread isn't the thread that create the object - */ - HRESULT assertCreatorThread() const - { - if (GetCurrentThreadId() != threadId) return UI_E_WRONG_THREAD; - return S_OK; - } - - /** - * @brief Get the Creator Thread Id - * - * @return DWORD creator thread id - */ - DWORD getCreatorThreadId() const - { - return threadId; - } - }; -}// namespace libtf diff --git a/src/include/tf/TfCompositionImpl.hpp b/src/include/tf/TfCompositionImpl.hpp deleted file mode 100644 index c9affc8..0000000 --- a/src/include/tf/TfCompositionImpl.hpp +++ /dev/null @@ -1,329 +0,0 @@ -#pragma once -#include - -#include "ComBSTR.hpp" -#include "TfInputContextImpl.hpp" - -namespace libtf { - class CompositionImpl : public IngameIME::Composition { - private: - DWORD cookieEditSink{TF_INVALID_COOKIE}; - DWORD cookieEleSink{TF_INVALID_COOKIE}; - - protected: - class CompositionHandler; - InputContextImpl* inputCtx; - ComPtr handler; - - protected: - class CompositionHandler : protected ComObjectBase, - public ITfContextOwnerCompositionSink, - public ITfTextEditSink, - public ITfEditSession, - public ITfUIElementSink { - protected: - CompositionImpl* comp; - - ComPtr compView; - - ComQIPtr eleMgr{IID_ITfUIElementMgr}; - ComPtr ele; - DWORD eleId{TF_INVALID_UIELEMENTID}; - - public: - CompositionHandler(CompositionImpl* comp) : comp(comp) - { - eleMgr = comp->inputCtx->threadMgr; - } - - public: - COM_DEF_BEGIN(); - COM_DEF_INF(ITfContextOwnerCompositionSink); - COM_DEF_INF(ITfTextEditSink); - COM_DEF_INF(ITfEditSession); - COM_DEF_INF(ITfUIElementSink); - COM_DEF_END(); - - public: - HRESULT STDMETHODCALLTYPE OnStartComposition(ITfCompositionView* pComposition, BOOL* pfOk) override - { - if (!pfOk) return E_INVALIDARG; - - // Always allow Composition start - *pfOk = true; - - comp->IngameIME::PreEditCallbackHolder::runCallback(IngameIME::CompositionState::Begin, nullptr); - - return S_OK; - } - - HRESULT STDMETHODCALLTYPE OnUpdateComposition(ITfCompositionView* pComposition, - ITfRange* pRangeNew) override - { - // Handle preedit in ITfTextEditSink - compView = pComposition; - - return S_OK; - } - - HRESULT STDMETHODCALLTYPE OnEndComposition(ITfCompositionView* pComposition) override - { - compView.reset(); - comp->IngameIME::PreEditCallbackHolder::runCallback(IngameIME::CompositionState::End, nullptr); - - static HRESULT hr; - hr = comp->inputCtx->ctx->RequestEditSession( - comp->inputCtx->clientId, this, TF_ES_ASYNC | TF_ES_READWRITE, &hr); - - return S_OK; - } - - public: - /** - * @brief Get PreEdit text and its selection - * - * @note Selection change only triggers OnEndEdit event, - * for convenient, we handle preedit here at the same time - */ - HRESULT STDMETHODCALLTYPE OnEndEdit(ITfContext* pic, TfEditCookie ec, ITfEditRecord* pEditRecord) override - { - COM_HR_BEGIN(S_OK); - - // No active composition - if (!compView) return S_OK; - - ComPtr preEditRange; - CHECK_HR(compView->GetRange(&preEditRange)); - - // Get preedit length - ComQIPtr rangeAcp(IID_ITfRangeACP, preEditRange); - LONG acpStart, len; - CHECK_HR(rangeAcp->GetExtent(&acpStart, &len)); - ULONG preEditLen = len; - auto bufPreEdit = std::make_unique(preEditLen); - // Get preedit text - CHECK_HR(preEditRange->GetText(ec, 0, bufPreEdit.get(), preEditLen, &preEditLen)); - - // Get selection of the preedit - TF_SELECTION sel[1]; - ULONG fetched; - ComPtr selRange; - CHECK_HR(comp->inputCtx->ctx->GetSelection(ec, TF_DEFAULT_SELECTION, 1, sel, &fetched)); - selRange.attach(sel[0].range); - rangeAcp = selRange; - CHECK_HR(rangeAcp->GetExtent(&acpStart, &len)); - - IngameIME::PreEditContext preEditCtx; - preEditCtx.selStart = acpStart; - preEditCtx.selEnd = acpStart + len; - preEditCtx.content = std::wstring(bufPreEdit.get(), preEditLen); - - comp->IngameIME::PreEditCallbackHolder::runCallback(IngameIME::CompositionState::Update, &preEditCtx); - - COM_HR_END(); - COM_HR_RET(); - } - - /** - * @brief Get all the text in the context, which is commit string - */ - HRESULT STDMETHODCALLTYPE DoEditSession(TfEditCookie ec) override - { - COM_HR_BEGIN(S_OK); - - auto inputCtx = comp->inputCtx; - auto ctx = inputCtx->ctx; - - // Get a range which covers all the texts in the context - ComPtr fullRange; - ComPtr rangeAtEnd; - CHECK_HR(ctx->GetStart(ec, &fullRange)); - CHECK_HR(ctx->GetEnd(ec, &rangeAtEnd)); - CHECK_HR(fullRange->ShiftEndToRange(ec, rangeAtEnd.get(), TF_ANCHOR_END)); - - // It's possible that the context is empty when there is no commit - BOOL isEmpty; - CHECK_HR(fullRange->IsEmpty(ec, &isEmpty)); - if (isEmpty) return S_OK; - - // Get the text length - ComQIPtr rangeAcp(IID_ITfRangeACP, fullRange); - LONG acpStart, len; - CHECK_HR(rangeAcp->GetExtent(&acpStart, &len)); - ULONG commitLen = len; - auto bufCommit = std::make_unique(commitLen); - // Get the commit text - CHECK_HR(fullRange->GetText(ec, 0, bufCommit.get(), commitLen, &commitLen)); - // Clear the texts in the text store - CHECK_HR(fullRange->SetText(ec, 0, NULL, 0)); - - comp->IngameIME::CommitCallbackHolder::runCallback(std::wstring(bufCommit.get(), commitLen)); - - COM_HR_END(); - COM_HR_RET(); - } - - public: - /** - * @brief Hide all the window of the input method if in full screen mode - */ - HRESULT STDMETHODCALLTYPE BeginUIElement(DWORD dwUIElementId, BOOL* pbShow) override - { - COM_HR_BEGIN(S_OK); - - if (dwUIElementId == TF_INVALID_UIELEMENTID) return E_INVALIDARG; - if (!pbShow) return E_INVALIDARG; - - auto inputCtx = comp->inputCtx; - - *pbShow = !inputCtx->fullscreen; - - ComPtr uiEle; - CHECK_HR(eleMgr->GetUIElement(dwUIElementId, &uiEle)); - - ComQIPtr candEle(IID_ITfCandidateListUIElement, uiEle); - // Check if current UIElement is CandidateListUIElement - if (candEle) { - ele = candEle; - eleId = dwUIElementId; - // Handle Candidate List events - comp->IngameIME::CandidateListCallbackHolder::runCallback(IngameIME::CandidateListState::Begin, - nullptr); - } - - COM_HR_END(); - COM_HR_RET(); - } - - HRESULT STDMETHODCALLTYPE UpdateUIElement(DWORD dwUIElementId) override - { - COM_HR_BEGIN(S_OK); - - if (dwUIElementId == TF_INVALID_UIELEMENTID) return E_INVALIDARG; - if (eleId == TF_INVALID_UIELEMENTID || dwUIElementId != eleId) return S_OK; - - // Total count of Candidates - uint32_t totalCount; - CHECK_HR(ele->GetCount(&totalCount)); - - // How many pages? - uint32_t pageCount; - CHECK_HR(ele->GetPageIndex(NULL, 0, &pageCount)); - - // Array of pages' start index - auto pageStarts = std::make_unique(pageCount); - CHECK_HR(ele->GetPageIndex(pageStarts.get(), pageCount, &pageCount)); - - // Current page's index in pageStarts - uint32_t curPage; - CHECK_HR(ele->GetCurrentPage(&curPage)); - - uint32_t pageStart = pageStarts[curPage]; - uint32_t pageEnd = curPage == pageCount - 1 ? totalCount : pageStarts[curPage + 1]; - uint32_t pageSize = pageEnd - pageStart; - - IngameIME::CandidateListContext candCtx; - - // Currently Selected Candidate's absolute index - UINT sel; - CHECK_HR(ele->GetSelection(&sel)); - // Absolute index to relative index - sel -= pageStart; - candCtx.selection = sel; - - // Get Candidate Strings - for (uint32_t i = pageStart; i < pageEnd; i++) { - ComBSTR candidate; - if (FAILED(ele->GetString(i, &candidate))) - candCtx.candidates.push_back(L"[err]"); - else - candCtx.candidates.push_back(candidate.bstr); - } - - comp->IngameIME::CandidateListCallbackHolder::runCallback(IngameIME::CandidateListState::Update, - &candCtx); - - COM_HR_END(); - COM_HR_RET(); - } - - HRESULT STDMETHODCALLTYPE EndUIElement(DWORD dwUIElementId) override - { - COM_HR_BEGIN(S_OK); - - if (dwUIElementId == TF_INVALID_UIELEMENTID) return E_INVALIDARG; - if (eleId == TF_INVALID_UIELEMENTID || dwUIElementId != eleId) return S_OK; - - eleId = TF_INVALID_UIELEMENTID; - ele.reset(); - - comp->IngameIME::CandidateListCallbackHolder::runCallback(IngameIME::CandidateListState::End, nullptr); - - COM_HR_END(); - COM_HR_RET(); - } - }; - - public: - CompositionImpl(InputContextImpl* inputCtx) : inputCtx(inputCtx) - { - COM_HR_BEGIN(S_OK); - - handler = new CompositionHandler(this); - - ComQIPtr eleMgr(IID_ITfUIElementMgr, inputCtx->threadMgr); - ComQIPtr source(IID_ITfSource, eleMgr); - CHECK_HR(source->AdviseSink( - IID_ITfUIElementSink, static_cast(handler.get()), &cookieEleSink)); - - // This EditCookie is useless - TfEditCookie ec; - CHECK_HR(inputCtx->docMgr->CreateContext(inputCtx->clientId, - 0, - static_cast(handler.get()), - &inputCtx->ctx, - &ec)); - - source = inputCtx->ctx; - CHECK_HR( - source->AdviseSink(IID_ITfTextEditSink, static_cast(handler.get()), &cookieEditSink)); - - COM_HR_END(); - COM_HR_THR(); - } - - ~CompositionImpl() - { - if (cookieEleSink != TF_INVALID_COOKIE) { - ComQIPtr eleMgr(IID_ITfUIElementMgr, inputCtx->threadMgr); - ComQIPtr source(IID_ITfSource, eleMgr); - source->UnadviseSink(cookieEleSink); - cookieEleSink = TF_INVALID_COOKIE; - } - - if (cookieEditSink != TF_INVALID_COOKIE) { - ComQIPtr source(IID_ITfSource, inputCtx->ctx); - source->UnadviseSink(cookieEditSink); - cookieEditSink = TF_INVALID_COOKIE; - } - } - - public: - /** - * @brief Terminate active composition - * - */ - virtual void terminate() override - { - COM_HR_BEGIN(S_OK); - - ComQIPtr services(IID_ITfContextOwnerCompositionServices, - inputCtx->ctx); - // Pass Null to terminate all the composition - services->TerminateComposition(NULL); - - COM_HR_END(); - COM_HR_THR(); - } - }; -}// namespace libtf diff --git a/src/include/tf/TfFunction.hpp b/src/include/tf/TfFunction.hpp deleted file mode 100644 index a3a4bfc..0000000 --- a/src/include/tf/TfFunction.hpp +++ /dev/null @@ -1,64 +0,0 @@ -#pragma once -#include -#include - -#include -#include - -namespace libtf { - typedef HRESULT(WINAPI* pTF_CreateThreadMgr)(ITfThreadMgr**); - typedef HRESULT(WINAPI* pTF_CreateInputProcessorProfiles)(ITfInputProcessorProfiles**); - typedef HRESULT(WINAPI* pTF_GetThreadMgr)(ITfThreadMgr**); - - auto getMsCtf() - { - return std::unique_ptr, decltype(&::FreeLibrary)>(LoadLibraryW(L"msctf.dll"), - ::FreeLibrary); - } - - /** - * @brief Create ThreadMgr for the calling thread without initializing COM - * - * ThreadMgr created by COM is using Apartment Model, which makes it problematic in MultiThreaded Model - */ - HRESULT createThreadMgr(ITfThreadMgr** pptim) - { - auto hMsCtf = getMsCtf(); - if (!hMsCtf) return E_FAIL; - auto pfn = (pTF_CreateThreadMgr)GetProcAddress(hMsCtf.get(), "TF_CreateThreadMgr"); - - return pfn ? (*pfn)(pptim) : E_FAIL; - } - - /** - * @brief Obtains a copy of ThreadMgr previously created within the calling thread, - * or create new if no previous one - */ - HRESULT getThreadMgr(ITfThreadMgr** pptim) - { - auto hMsCtf = getMsCtf(); - if (!hMsCtf) return E_FAIL; - auto pfn = (pTF_GetThreadMgr)GetProcAddress(hMsCtf.get(), "TF_GetThreadMgr"); - if (!pfn) return E_FAIL; - - HRESULT hr; - - if (SUCCEEDED(hr = (*pfn)(pptim))) return hr; - - return createThreadMgr(pptim); - } - - /** - * @brief Create InputProcessorProfiles for the calling thread without initializing COM - * - * InputProcessorProfiles created by COM is using Apartment Model, which makes it problematic in MultiThreaded Model - */ - HRESULT createInputProcessorProfiles(ITfInputProcessorProfiles** ppipr) - { - auto hMsCtf = getMsCtf(); - if (!hMsCtf) return E_FAIL; - auto pfn = (pTF_CreateInputProcessorProfiles)GetProcAddress(hMsCtf.get(), "TF_CreateInputProcessorProfiles"); - - return pfn ? (*pfn)(ppipr) : E_FAIL; - } -}// namespace libtf \ No newline at end of file diff --git a/src/include/tf/TfIngameIMEImpl.hpp b/src/include/tf/TfIngameIMEImpl.hpp deleted file mode 100644 index ae4126c..0000000 --- a/src/include/tf/TfIngameIMEImpl.hpp +++ /dev/null @@ -1,84 +0,0 @@ -#pragma once -#include "IngameIME.hpp" - -#include "TfInputContextImpl.hpp" -#include "TfInputProcessorImpl.hpp" - -namespace libtf { - class GlobalImpl : public IngameIME::Global { - private: - DWORD cookieComp{TF_INVALID_COOKIE}; - DWORD cookieProc{TF_INVALID_COOKIE}; - - protected: - ComPtr handler; - friend class IngameIME::Global; - - protected: - GlobalImpl() - { - COM_HR_BEGIN(S_OK); - - handler = new InputProcessorHandler(); - - ComQIPtr source(IID_ITfSource, handler->mode); - CHECK_HR(source->AdviseSink( - IID_ITfCompartmentEventSink, static_cast(handler.get()), &cookieComp)); - - source = handler->compMgr; - CHECK_HR(source->AdviseSink(IID_ITfInputProcessorProfileActivationSink, - static_cast(handler.get()), - &cookieProc)); - - COM_HR_END(); - COM_HR_THR(); - } - - ~GlobalImpl() - { - if (cookieProc != TF_INVALID_COOKIE) { - ComQIPtr source(IID_ITfSource, handler); - source->UnadviseSink(cookieProc); - cookieProc = TF_INVALID_COOKIE; - } - - if (cookieComp != TF_INVALID_COOKIE) { - ComQIPtr source(IID_ITfSource, handler->mode); - source->UnadviseSink(cookieComp); - cookieComp = TF_INVALID_COOKIE; - } - } - - public: - /** - * @brief Get Active InputProcessor - * - * @return std::shared_ptr - */ - virtual std::shared_ptr getActiveInputProcessor() const override - { - return IngameIME::InputProcessorImpl::getActiveInputProcessor(); - } - - /** - * @brief Get system availiable InputProcessor - * - * @return std::list> - */ - virtual std::list> getInputProcessors() const override - { - return IngameIME::InputProcessorImpl::getInputProcessors(); - } - - /** - * @brief Get the InputContext object - * - * @param hWnd the window to create InputContext - * @return std::shared_ptr - */ - virtual std::shared_ptr getInputContext(void* hWnd, ...) override - { - return std::make_shared(reinterpret_cast(hWnd)); - } - }; -}// namespace libtf \ No newline at end of file diff --git a/src/include/tf/TfInputContextImpl.hpp b/src/include/tf/TfInputContextImpl.hpp deleted file mode 100644 index d62c71e..0000000 --- a/src/include/tf/TfInputContextImpl.hpp +++ /dev/null @@ -1,212 +0,0 @@ -#pragma once -#include - -#include "InputContextImpl.hpp" -#include "InputProcessor.hpp" - -#include - -#include "ComObjectBase.hpp" -#include "ComPtr.hpp" -#include "IThreadAssociate.hpp" -#include "TfFunction.hpp" - -namespace libtf { - class InputContextImpl : public IngameIME::InputContext, public IThreadAssociate { - protected: - class ContextOwner; - - protected: - ComPtr threadMgr; - TfClientId clientId{TF_CLIENTID_NULL}; - ComPtr docMgr; - ComPtr emptyDocMgr; - ComPtr ctx; - - HWND hWnd; - ComPtr owner; - - bool activated{false}; - bool fullscreen{false}; - - friend class CompositionImpl; - - private: - DWORD cookie{TF_INVALID_COOKIE}; - - protected: - class ContextOwner : protected ComObjectBase, public ITfContextOwner { - protected: - InputContextImpl* ctx; - - public: - ContextOwner(InputContextImpl* ctx) : ctx(ctx) {} - - public: - COM_DEF_BEGIN(); - COM_DEF_INF(ITfContextOwner); - COM_DEF_END(); - - public: - /** - * @brief Our implementation does not calculate a text layout - * - * @return HRESULT TS_E_NOLAYOUT - */ - HRESULT STDMETHODCALLTYPE GetACPFromPoint(const POINT* ptScreen, DWORD dwFlags, LONG* pacp) override - { - return TS_E_NOLAYOUT; - } - - /** - * @brief Input method call this method to get the bounding box, in screen coordinates, of preedit string, - * and use which to position its candidate window - */ - HRESULT STDMETHODCALLTYPE GetTextExt(LONG acpStart, LONG acpEnd, RECT* prc, BOOL* pfClipped) override - { - // Fetch bounding box - IngameIME::InternalRect box; - ctx->comp->IngameIME::PreEditRectCallbackHolder::runCallback(box); - *prc = box; - - // Map window coordinate to screen coordinate - MapWindowPoints(ctx->hWnd, NULL, (LPPOINT)prc, 2); - - return S_OK; - } - - /** - * @brief Return the bounding box, in screen coordinates, of the display surface of the text stream - */ - HRESULT STDMETHODCALLTYPE GetScreenExt(RECT* prc) override - { - GetWindowRect(ctx->hWnd, prc); - - return S_OK; - } - - /** - * @brief Obtains the status of the context. - */ - HRESULT STDMETHODCALLTYPE GetStatus(TF_STATUS* pdcs) override - { - // Set to 0 indicates the context is editable - pdcs->dwDynamicFlags = 0; - // Set to 0 indicates the context only support single selection - pdcs->dwStaticFlags = 0; - return S_OK; - } - - /** - * @brief Return the window handle the InputContext is associated to - * - * Retrive this value by calling ITfContextView::GetWnd, ITfContextView can query from ITfContext - */ - HRESULT STDMETHODCALLTYPE GetWnd(HWND* phwnd) override - { - *phwnd = ctx->hWnd; - - return S_OK; - } - - /** - * @brief Our implementation doesn't support any attributes, just return VT_EMPTY - */ - HRESULT STDMETHODCALLTYPE GetAttribute(REFGUID rguidAttribute, VARIANT* pvarValue) override - { - pvarValue->vt = VT_EMPTY; - return S_OK; - } - }; - - public: - InputContextImpl(HWND hWnd); - ~InputContextImpl() - { - if (cookie != TF_INVALID_COOKIE) { - ComQIPtr source(IID_ITfSource, ctx); - source->UnadviseSink(cookie); - cookie = TF_INVALID_COOKIE; - owner.reset(); - } - - if (ctx) { - setActivated(false); - comp.reset(); - docMgr->Pop(TF_POPF_ALL); - ctx.reset(); - } - - if (docMgr) { - docMgr.reset(); - emptyDocMgr.reset(); - } - - if (clientId != TF_CLIENTID_NULL) { - threadMgr->Deactivate(); - threadMgr.reset(); - clientId = TF_CLIENTID_NULL; - } - } - - public: - /** - * @brief Set if context activated - * - * @param activated set to true to activate input method - * @throw UI_E_WRONG_THREAD if the calling thread isn't the thread that create the context - */ - virtual void setActivated(const bool activated) override - { - COM_HR_BEGIN(S_OK); - - CHECK_HR(assertCreatorThread()); - - this->activated = activated; - - ComPtr prevDocumentMgr; - if (activated) { CHECK_HR(threadMgr->AssociateFocus(hWnd, docMgr.get(), &prevDocumentMgr)); } - else { - // Focus on empty context docMgr can deactivate input method - CHECK_HR(threadMgr->AssociateFocus(hWnd, emptyDocMgr.get(), &prevDocumentMgr)); - // Terminate active composition - comp->terminate(); - } - - COM_HR_END(); - COM_HR_THR(); - } - - /** - * @brief Get context activate state - * - * @return if the context activated - */ - virtual bool getActivated() const override - { - return activated; - } - - /** - * @brief Set InputContext full screen state - * - * @param fullscreen if InputContext full screen - */ - virtual void setFullScreen(const bool fullscreen) override - { - this->fullscreen = fullscreen; - if (activated) comp->terminate(); - } - - /** - * @brief Get if InputContext in full screen state - * - * @return true full screen mode - * @return false window mode - */ - virtual bool getFullScreen() const override - { - return fullscreen; - } - }; -}// namespace libtf \ No newline at end of file diff --git a/src/include/tf/TfInputProcessorImpl.hpp b/src/include/tf/TfInputProcessorImpl.hpp deleted file mode 100644 index b543098..0000000 --- a/src/include/tf/TfInputProcessorImpl.hpp +++ /dev/null @@ -1,106 +0,0 @@ -#pragma once -#include "InputProcessorImpl.hpp" - -namespace libtf { - class InputProcessorHandler : public ComObjectBase, - public ITfInputProcessorProfileActivationSink, - public ITfCompartmentEventSink { - public: - ComQIPtr compMgr{IID_ITfCompartmentMgr}; - ComPtr mode; - - public: - InputProcessorHandler() - { - COM_HR_BEGIN(S_OK); - - ComPtr threadMgr; - CHECK_HR(getThreadMgr(&threadMgr)); - compMgr = threadMgr; - - CHECK_HR(compMgr->GetCompartment(GUID_COMPARTMENT_KEYBOARD_INPUTMODE_CONVERSION, &mode)); - - COM_HR_END(); - COM_HR_THR(); - } - - protected: - IngameIME::InputProcessorContext getCtx() - { - IngameIME::InputProcessorContext result; - - auto activeProc = IngameIME::InputProcessorImpl::getActiveInputProcessor(); - result.proc = activeProc; - - VARIANT var; - mode->GetValue(&var); - - if (activeProc->type == IngameIME::InputProcessorType::KeyboardLayout) - result.modes.push_back(L"AlphaNumeric"); - else { - if (var.intVal & TF_CONVERSIONMODE_NATIVE) { - result.modes.push_back(L"Native"); - - if (activeProc->isJap) - if (var.intVal & TF_CONVERSIONMODE_KATAKANA) - result.modes.push_back(L"Katakana"); - else - result.modes.push_back(L"Hiragana"); - } - else - result.modes.push_back(L"AlphaNumeric"); - - if (var.intVal & TF_CONVERSIONMODE_FULLSHAPE) - result.modes.push_back(L"FullShape"); - else - result.modes.push_back(L"HalfShape"); - } - - return result; - } - - public: - COM_DEF_BEGIN(); - COM_DEF_INF(ITfInputProcessorProfileActivationSink); - COM_DEF_INF(ITfCompartmentEventSink); - COM_DEF_END(); - - HRESULT STDMETHODCALLTYPE OnActivated(DWORD dwProfileType, - LANGID langid, - REFCLSID clsid, - REFGUID catid, - REFGUID guidProfile, - HKL hkl, - DWORD dwFlags) - { - COM_HR_BEGIN(S_OK); - - // Only notify active inputprocessor - if (!(dwFlags & TF_IPSINK_FLAG_ACTIVE)) return S_OK; - - TF_INPUTPROCESSORPROFILE profile; - profile.dwProfileType = dwProfileType; - profile.langid = langid; - profile.clsid = clsid; - profile.catid = catid; - profile.guidProfile = guidProfile; - profile.hkl = hkl; - - IngameIME::Global::getInstance().runCallback(IngameIME::InputProcessorState::FullUpdate, getCtx()); - - COM_HR_END(); - COM_HR_RET(); - } - - HRESULT STDMETHODCALLTYPE OnChange(REFGUID rguid) override - { - COM_HR_BEGIN(S_OK); - - if (IsEqualGUID(rguid, GUID_COMPARTMENT_KEYBOARD_INPUTMODE_CONVERSION)) - IngameIME::Global::getInstance().runCallback(IngameIME::InputProcessorState::InputModeUpdate, getCtx()); - - COM_HR_END(); - COM_HR_RET(); - } - }; -}// namespace libtf \ No newline at end of file diff --git a/testWnd/CMakeLists.txt b/testWnd/CMakeLists.txt index 6f10c30..4f07bcc 100644 --- a/testWnd/CMakeLists.txt +++ b/testWnd/CMakeLists.txt @@ -4,8 +4,17 @@ project(testWnd) add_subdirectory(glfw EXCLUDE_FROM_ALL) add_subdirectory(glad EXCLUDE_FROM_ALL) +file(GLOB_RECURSE source_file ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp) + add_executable(testWnd ${CMAKE_CURRENT_SOURCE_DIR}/src/main.cpp) -target_sources(testWnd PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src) +target_include_directories( + testWnd + PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/include + ${CMAKE_CURRENT_SOURCE_DIR}/glfw/include + ${CMAKE_CURRENT_SOURCE_DIR}/glad/include +) +target_sources(testWnd PRIVATE "${source_file}") target_link_libraries(testWnd PRIVATE glad_static glfw IngameIME_Static) # Install diff --git a/testWnd/include/InputWindow.hpp b/testWnd/include/InputWindow.hpp new file mode 100644 index 0000000..fa0810c --- /dev/null +++ b/testWnd/include/InputWindow.hpp @@ -0,0 +1,37 @@ +#pragma once +#include +#include +#include + +#include +#include + +#define GLFW_EXPOSE_NATIVE_WIN32 +#include + +#include "IngameIME.hpp" + +class InputWindow +{ + public: + static std::map WindowMap; + + public: + int width; + int height; + GLFWwindow* window; + std::shared_ptr inputCtx; + bool fullscreen = false; + + std::shared_ptr initialProc; + + public: + static InputWindow* getByGLFWwindow(GLFWwindow* window); + + public: + InputWindow(); + + public: + bool pollEvent(); + void switchFullScreen(); +}; diff --git a/testWnd/src/InputWindow.cpp b/testWnd/src/InputWindow.cpp new file mode 100644 index 0000000..adc6ac2 --- /dev/null +++ b/testWnd/src/InputWindow.cpp @@ -0,0 +1,198 @@ +#include "InputWindow.hpp" + +std::map InputWindow::WindowMap = std::map(); + +void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods) +{ + // Close Window + if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) + { + glfwSetWindowShouldClose(window, GLFW_TRUE); + } + // Switch Fullscreen + if (glfwGetKey(window, GLFW_KEY_F11) == GLFW_PRESS) + { + InputWindow::getByGLFWwindow(window)->switchFullScreen(); + } + // Switch Activate + if (glfwGetKey(window, GLFW_KEY_F5) == GLFW_PRESS) + { + auto inputCtx = InputWindow::getByGLFWwindow(window)->inputCtx; + inputCtx->setActivated(!inputCtx->getActivated()); + printf("Activated:%s\n", inputCtx->getActivated() ? "True" : "False"); + } + // Activate Initial InputProc + if (glfwGetKey(window, GLFW_KEY_F3) == GLFW_PRESS) + { + InputWindow::getByGLFWwindow(window)->initialProc->setActivated(); + } +} + +void window_size_callback(GLFWwindow* window, int width, int height) +{ + auto inputWindow = InputWindow::getByGLFWwindow(window); + if (!inputWindow->fullscreen) + { + inputWindow->width = width; + inputWindow->height = height; + } +} + +void framebuffer_size_callback(GLFWwindow* window, int width, int height) +{ + glViewport(0, 0, width, height); +} + +void debugShowInputProcessor(std::shared_ptr proc) +{ + printf("[%s][%ls][%ls]: %ls\n", + proc->type == IngameIME::InputProcessorType::KeyboardLayout ? "KL" : "TIP", + proc->locale->locale.c_str(), + proc->locale->name.c_str(), + proc->name.c_str()); +} + +InputWindow* InputWindow::getByGLFWwindow(GLFWwindow* window) +{ + return WindowMap[window]; +} + +InputWindow::InputWindow() +{ + window = glfwCreateWindow(width = 800, height = 600, "libtf", NULL, NULL); + WindowMap[window] = this; + + if (!window) throw std::runtime_error("Failed to create window"); + + glfwMakeContextCurrent(window); + glfwSetKeyCallback(window, key_callback); + glfwSetWindowSizeCallback(window, window_size_callback); + glfwSetFramebufferSizeCallback(window, framebuffer_size_callback); + + // glad: load all OpenGL function pointers + if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) throw std::runtime_error("Failed to initialize GLAD"); + + // Print library version + printf("IngameIME Library Version: %s\n", IngameIME::Global::Version.c_str()); + + // Create InputContext + inputCtx = IngameIME::Global::getInstance().getInputContext(glfwGetWin32Window(window)); + + // Register callbacks + inputCtx->comp->IngameIME::PreEditCallbackHolder::setCallback( + [this](auto&& state, auto&& ctx) + { + switch (state) + { + case IngameIME::CompositionState::Begin: + printf("Composition Begin!\n"); + break; + case IngameIME::CompositionState::Update: + printf("Composition Update!\n"); + printf("PreEdit: %ls\n", ctx->content.c_str()); + printf("Sel: %d-%d\n", ctx->selStart, ctx->selEnd); + break; + case IngameIME::CompositionState::End: + printf("Composition End\n"); + break; + } + }); + + inputCtx->comp->IngameIME::CommitCallbackHolder::setCallback( + [this](auto&& commit) + { + // Show commit + printf("Commit: %ls\n", commit.c_str()); + }); + + inputCtx->comp->IngameIME::PreEditRectCallbackHolder::setCallback( + [this](auto&& rect) + { + rect.left = 20; + rect.top = 20; + rect.right = 20; + rect.bottom = 20; + printf("Update PreEditRect\n"); + }); + + inputCtx->comp->IngameIME::CandidateListCallbackHolder::setCallback( + [this](auto&& state, auto&& ctx) + { + switch (state) + { + case IngameIME::CandidateListState::Begin: + printf("CandidateList Begin!\n"); + break; + case IngameIME::CandidateListState::Update: + printf("CandidateList Update!\n"); + printf("Selection: %d\n", ctx->selection); + for (auto&& cand : ctx->candidates) + { + printf("%ls\n", cand.c_str()); + } + break; + case IngameIME::CandidateListState::End: + printf("CandidateList End!\n"); + break; + } + }); + + IngameIME::Global::getInstance().setCallback( + [this](auto&& state, auto&& ctx) + { + switch (state) + { + case IngameIME::InputProcessorState::FullUpdate: + printf("InputProcessor Full Update!\n"); + break; + case IngameIME::InputProcessorState::InputModeUpdate: + printf("InputProcessor InputMode Update!\n"); + break; + } + + debugShowInputProcessor(ctx.proc); + + printf("InputModes:\n"); + for (auto&& mode : ctx.modes) + { + printf("%ls\n", mode.c_str()); + } + }); + + printf("Active InputProc:\n"); + initialProc = IngameIME::Global::getInstance().getActiveInputProcessor(); + debugShowInputProcessor(initialProc); + + printf("InputProcs:\n"); + for (auto proc : IngameIME::Global::getInstance().getInputProcessors()) + { + debugShowInputProcessor(proc); + } + + printf("Press F3 to activate the InputProcessor recored when inital\n"); + printf("Press F5 to switch on/off InputContext\n"); + printf("Press F11 to switch on/off fullscreen\n"); +} + +bool InputWindow::pollEvent() +{ + glClearColor(0.2f, 0.3f, 0.3f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + glfwSwapBuffers(window); + glfwPollEvents(); + + return !glfwWindowShouldClose(window); +} + +void InputWindow::switchFullScreen() +{ + fullscreen = !fullscreen; + // Update FullScreen state + inputCtx->setFullScreen(fullscreen); + + printf("FullScreen:%s\n", fullscreen ? "True" : "False"); + + auto monitor = glfwGetPrimaryMonitor(); + int monitorWidth, monitorHeight; + glfwGetMonitorWorkarea(monitor, NULL, NULL, &monitorWidth, &monitorHeight); +} diff --git a/testWnd/src/InputWindow.hpp b/testWnd/src/InputWindow.hpp deleted file mode 100644 index 2b14635..0000000 --- a/testWnd/src/InputWindow.hpp +++ /dev/null @@ -1,192 +0,0 @@ -#pragma once -#include -#include -#include - -#include -#include - -#define GLFW_EXPOSE_NATIVE_WIN32 -#include - -#include "IngameIME.hpp" - -void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods); -void window_size_callback(GLFWwindow* window, int width, int height); -void framebuffer_size_callback(GLFWwindow* window, int width, int height) -{ - glViewport(0, 0, width, height); -} - -void debugShowInputProcessor(std::shared_ptr proc) -{ - printf("[%s][%ls][%ls]: %ls\n", - proc->type == IngameIME::InputProcessorType::KeyboardLayout ? "KL" : "TIP", - proc->locale->locale.c_str(), - proc->locale->name.c_str(), - proc->name.c_str()); -} - -class InputWindow { - public: - static std::map WindowMap; - - public: - int width; - int height; - GLFWwindow* window; - std::shared_ptr inputCtx; - bool fullscreen = false; - - std::shared_ptr initialProc; - - public: - static InputWindow* getByGLFWwindow(GLFWwindow* window) - { - return WindowMap[window]; - } - - public: - InputWindow() - { - window = glfwCreateWindow(width = 800, height = 600, "libtf", NULL, NULL); - WindowMap[window] = this; - - if (!window) throw std::runtime_error("Failed to create window"); - - glfwMakeContextCurrent(window); - glfwSetKeyCallback(window, key_callback); - glfwSetWindowSizeCallback(window, window_size_callback); - glfwSetFramebufferSizeCallback(window, framebuffer_size_callback); - - // glad: load all OpenGL function pointers - if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) throw std::runtime_error("Failed to initialize GLAD"); - - // Print library version - printf("IngameIME Library Version: %s\n", IngameIME::Global::Version.c_str()); - - // Create InputContext - inputCtx = IngameIME::Global::getInstance().getInputContext(glfwGetWin32Window(window)); - - // Register callbacks - inputCtx->comp->IngameIME::PreEditCallbackHolder::setCallback([this](auto&& state, auto&& ctx) { - switch (state) { - case IngameIME::CompositionState::Begin: printf("Composition Begin!\n"); break; - case IngameIME::CompositionState::Update: - printf("Composition Update!\n"); - printf("PreEdit: %ls\n", ctx->content.c_str()); - printf("Sel: %d-%d\n", ctx->selStart, ctx->selEnd); - break; - case IngameIME::CompositionState::End: printf("Composition End\n"); break; - } - }); - - inputCtx->comp->IngameIME::CommitCallbackHolder::setCallback([this](auto&& commit) { - // Show commit - printf("Commit: %ls\n", commit.c_str()); - }); - - inputCtx->comp->IngameIME::PreEditRectCallbackHolder::setCallback([this](auto&& rect) { - rect.left = 20; - rect.top = 20; - rect.right = 20; - rect.bottom = 20; - printf("Update PreEditRect\n"); - }); - - inputCtx->comp->IngameIME::CandidateListCallbackHolder::setCallback([this](auto&& state, auto&& ctx) { - switch (state) { - case IngameIME::CandidateListState::Begin: printf("CandidateList Begin!\n"); break; - case IngameIME::CandidateListState::Update: - printf("CandidateList Update!\n"); - printf("Selection: %d\n", ctx->selection); - for (auto&& cand : ctx->candidates) { printf("%ls\n", cand.c_str()); } - break; - case IngameIME::CandidateListState::End: printf("CandidateList End!\n"); break; - } - }); - - IngameIME::Global::getInstance().setCallback([this](auto&& state, auto&& ctx) { - switch (state) { - case IngameIME::InputProcessorState::FullUpdate: printf("InputProcessor Full Update!\n"); break; - case IngameIME::InputProcessorState::InputModeUpdate: - printf("InputProcessor InputMode Update!\n"); - break; - } - - debugShowInputProcessor(ctx.proc); - - printf("InputModes:\n"); - for (auto&& mode : ctx.modes) { printf("%ls\n", mode.c_str()); } - }); - - printf("Active InputProc:\n"); - initialProc = IngameIME::Global::getInstance().getActiveInputProcessor(); - debugShowInputProcessor(initialProc); - - printf("InputProcs:\n"); - for (auto proc : IngameIME::Global::getInstance().getInputProcessors()) { debugShowInputProcessor(proc); } - - printf("Press F3 to activate the InputProcessor recored when inital\n"); - printf("Press F5 to switch on/off InputContext\n"); - printf("Press F11 to switch on/off fullscreen\n"); - } - - public: - void runEventLoop() - { - while (!glfwWindowShouldClose(window)) { - glClearColor(0.2f, 0.3f, 0.3f, 1.0f); - glClear(GL_COLOR_BUFFER_BIT); - glfwSwapBuffers(window); - glfwPollEvents(); - } - } - void switchFullScreen() - { - fullscreen = !fullscreen; - // Update FullScreen state - inputCtx->setFullScreen(fullscreen); - - printf("FullScreen:%s\n", fullscreen ? "True" : "False"); - - auto monitor = glfwGetPrimaryMonitor(); - int monitorWidth, monitorHeight; - glfwGetMonitorWorkarea(monitor, NULL, NULL, &monitorWidth, &monitorHeight); - - // if (fullscreen) { glfwSetWindowMonitor(window, monitor, 0, 0, monitorWidth, monitorHeight, GLFW_DONT_CARE); } - // else - // glfwSetWindowMonitor( - // window, NULL, (monitorWidth - width) / 2, (monitorHeight - height) / 2, width, height, - // GLFW_DONT_CARE); - } -}; - -std::map InputWindow::WindowMap = std::map(); - -void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods) -{ - // Close Window - if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) { glfwSetWindowShouldClose(window, GLFW_TRUE); } - // Switch Fullscreen - if (glfwGetKey(window, GLFW_KEY_F11) == GLFW_PRESS) { InputWindow::getByGLFWwindow(window)->switchFullScreen(); } - // Switch Activate - if (glfwGetKey(window, GLFW_KEY_F5) == GLFW_PRESS) { - auto inputCtx = InputWindow::getByGLFWwindow(window)->inputCtx; - inputCtx->setActivated(!inputCtx->getActivated()); - printf("Activated:%s\n", inputCtx->getActivated() ? "True" : "False"); - } - // Activate Initial InputProc - if (glfwGetKey(window, GLFW_KEY_F3) == GLFW_PRESS) { - InputWindow::getByGLFWwindow(window)->initialProc->setActivated(); - } -} - -void window_size_callback(GLFWwindow* window, int width, int height) -{ - auto inputWindow = InputWindow::getByGLFWwindow(window); - if (!inputWindow->fullscreen) { - inputWindow->width = width; - inputWindow->height = height; - } -} diff --git a/testWnd/src/main.cpp b/testWnd/src/main.cpp index 134deec..8136377 100644 --- a/testWnd/src/main.cpp +++ b/testWnd/src/main.cpp @@ -14,7 +14,8 @@ void error_callback(int error, const char* description) int main() { glfwSetErrorCallback(error_callback); - if (glfwInit()) { + if (glfwInit()) + { glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); @@ -22,14 +23,18 @@ int main() // Must call once to set active locale setlocale(LC_ALL, ""); - try { + try + { auto window = std::make_unique(); - window->runEventLoop(); + while (window->pollEvent()) + ; } - catch (std::exception& e) { + catch (std::exception& e) + { std::cerr << e.what() << '\n'; } - catch (...) { + catch (...) + { std::cerr << "err occurs!" << '\n'; }