Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reopen focus listener PR. #57

Merged
merged 22 commits into from
Jan 5, 2025

Conversation

LitnhJacuzzi
Copy link
Contributor

No description provided.

@LitnhJacuzzi
Copy link
Contributor Author

@reserveword 已经完成新增功能的同步,但暂未改动forge侧的内容,若与fabric侧使用同类实现方式应该也能兼容1.19.4~1.21

@reserveword
Copy link
Owner

做好准备之后修复一下冲突我就可以测试合入了

@LitnhJacuzzi
Copy link
Contributor Author

我这里显示无法线上修复冲突,你那边是否方便操作?如果太麻烦强制覆写应该也可以

@reserveword
Copy link
Owner

我这里显示无法线上修复冲突,你那边是否方便操作?如果太麻烦强制覆写应该也可以

你可以拉取到本地之后手动合并修复冲突

@LitnhJacuzzi
Copy link
Contributor Author

@reserveword 已修复冲突,暂未测试forge的兼容性

@LitnhJacuzzi
Copy link
Contributor Author

目前测试下来forge没有任何类注入成功,但日志里啥也没说就很难绷


public static FocusableWidgetAccessor focusedInputWidget = null;

private static boolean axiomGuiCaptureKeyboard = false;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

axiom相关判断的代码最好能封装起来,不要影响主流程里的东西

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这个地方不是很好封装,axiom使用的ImGui在捕获键盘时会阻止所有按键发送到MC,需要根据这个状态来决定是否检测MC组件的状态,ImGui捕获键盘的时候MC组件的状态是无意义的

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

我的想法是提供一个规则接口,返回三个状态(积极开启、积极关闭、未设置)中的一个
如果你觉得麻烦的话我后续慢慢改吧
感觉这么设计下去要开始抄iptables了(捂脸

@reserveword
Copy link
Owner

我好像给你提了不少检视意见,你要是懒得改我就过几天慢慢改吧

@LitnhJacuzzi
Copy link
Contributor Author

目前forge侧在工作空间里运行客户端没有问题,但构建出来的mod始终不会加载注入,正在寻找解决办法

@LitnhJacuzzi
Copy link
Contributor Author

已找到forge正确的mixin配置方式,要在manifest里声明配置文件。初步测试forge侧不兼容1.20以上的版本,如果每个加载器的兼容版本范围不同则建议每个加载器一个mod文件,不再合并为一个文件

@LitnhJacuzzi
Copy link
Contributor Author

LitnhJacuzzi commented Oct 24, 2024

合并后要注意以下几个问题:
1.记得fabric每次构建后手动替换mod根目录下的混淆映射文件(暂时以声明多个方法目标名解决)
2.fabric 1.21.1开始默认白名单有一定问题(会把主菜单界面纳入白名单),请注意检查修改
3.使用焦点监听后大部分配置项都不再需要(仅保留如白名单列表的项目)
4.Readme中的原理部分需要重新撰写
5.已确认可以解决除#6 外的所有Issue(#22 彗星端本不支持输入中文字符,需要安装其汉化版

@LitnhJacuzzi
Copy link
Contributor Author

关于聊天栏自动识别命令切换中英文的实现:Windows下似乎在输入法解除禁用50毫秒内不能立即切换中英文状态,只能被迫推迟切换操作

@LitnhJacuzzi
Copy link
Contributor Author

Linux下可以通过调用终端命令实现输入法控制,但这种方式开销较大故不能每游戏刻都检测输入法状态是否受到外部更改。
至此已解决所有主要Issue,Fixes #6, #13, #22, #27, #36, #46, #50, #56, #61

@litwak913
Copy link
Contributor

可以使用 dbus-java ,通过 D-Bus 去调用 Fcitx 和 ibus 的相关接口操作输入法,性能和效率比子进程要好,开销也小。

@LitnhJacuzzi
Copy link
Contributor Author

@litwak913 我最初尝试使用DBus来实现,但发现这种方法不得不将dbus-java一起打包到mod文件中使mod体积膨胀几十倍。考虑到只要没有除用户外的因素改变输入法状态就不用执行输入法检测,即使偶尔受到外部影响也可以通过“使文本框失去焦点再获得焦点”的方法触发一次检测,我们需要权衡用暴增的mod文件大小来换取一些性能是否弊大于利

@reserveword reserveword merged commit 5d9b7a1 into reserveword:1.19.4 Jan 5, 2025
@reserveword
Copy link
Owner

我在后续更新中进行了一些代码简化,希望你能看一下我后续的修改有没有改变你的核心设计

@LitnhJacuzzi
Copy link
Contributor Author

先说两个最重要的问题:
1.最新版本(5.0.0)在1.21不经过任何配置的情况下模组功能完全不生效(话说你发布之前不自己测试一下的吗)
2.编译时使用Java21会导致1.21之前的版本不能以首选Java版本(17)启动,虽然这可以手动指定Java版本解决但最好直接使用Java17编译

等我有时间再去代码里细看

@reserveword
Copy link
Owner

当前版本刚启动的时候有点问题,多切换几次窗口就好了。另外如果不生效也可以自己切换一下输入法就好了,我测试的时候是生效的(可能覆盖不够全面?)

@reserveword
Copy link
Owner

至于我为什么有问题不修就发布呢,因为这些问题你不加mod玩也会有

@LitnhJacuzzi
Copy link
Contributor Author

我自己正在使用的是我fork里最新发布的版本,用到现在没有出现任何问题,模组的所有行为都符合期望,如果你要对代码进行任何大规模的修改请至少保证模组的功能表现能达到这个版本的标准,从代码增减指标看(除去格式化那个提交)是进行了较大改动的

从改动最多那次提交的changelog看我先提出几点意见:
1.不要以setConversionStatus作为所有情况下的控制方式,这会导致每游戏刻都要检测中英文状态是否被shift改变并显著增加复杂度
2.此模组的功能实现不建议使用基于优先级的规则列表模式,这种模式与通用焦点系统的管理策略是不兼容的,容易产生更多难以解决的设计问题并增加维护难度
3.若解除ImGui的接口封装则需要注意保证未安装Axiom时运行时不会访问到这个类否则会导致异常

无论如何设计,输入法控制都要遵循两个核心原理:最终处理键盘输入的组件类型及其状态决定了输入法应该被设置为什么状态;最终响应键盘输入的组件由当前接收键盘事件的焦点系统此焦点系统内指定的焦点组件共同决定,一个焦点系统内只能有一个焦点组件(或等效的焦点组件,如白名单屏幕)

这几天我没有多少时间处理代码细节上的问题,你先根据这些建议尽量改进一下。另外我个人都是测试到不会触发任何漏洞期望之外的行为才会发布新的版本,不要给用户添麻烦是我们的义务

@reserveword
Copy link
Owner

你所提的几条意见我并不是完全没有考虑。我现在依次进行解释。

1.不要以setConversionStatus作为所有情况下的控制方式,这会导致每游戏刻都要检测中英文状态是否被shift改变并显著增加复杂度

我们在IMManager中放了一个state字段,游戏内对中英文状态的检测被缓存到这个字段上了,只有当这个字段被修改的时候才会调用PlatformIMManager进行实际的修改。玩家主动使用shift之类快捷键更改中英文在这个环境下对我们是不可见的,我们不需要检测玩家更改中英文的行为并改回来。

这一设计的原则是:玩家知道自己想要中文还是英文,所以当玩家修改中英文的时候我们会装作没看见,直到输入环境发生变化时我们再把输入法调整为适应新环境的状态。

当然你提到shift修改中英文这一点代入到游戏环境中确实有些问题,大部分玩家可能不会特意修改快捷键,默认的shift快捷键可能导致频繁意外切换。我后续会把这部分改回来。

2.此模组的功能实现不建议使用基于优先级的规则列表模式,这种模式与通用焦点系统的管理策略是不兼容的,容易产生更多难以解决的设计问题并增加维护难度

这两个模式根本没有不兼容的问题,焦点系统作为列表里的一个规则就可以了。事实上规则列表基本上就是把每个规则的代码挪到自己的类里面,然后把每tick执行的代码放到apply函数里面,最后依次执行这些apply函数罢了。

从另一个角度也可以解释为什么优先级系统(或其他等效的设计)在这里是不可避免的:我们有很多方法可以识别一个输入组件(Axiom的特殊规则、白名单列表规则、聊天栏规则、焦点规则),这些规则都是分别独立运行的。虽然你可以说我们所有的规则都是焦点规则的一部分,但是如何判断这个焦点到底落在什么地方呢?不还是得先看某一个规则有没有获取焦点,再看看另一个规则有没有获取焦点,最后所有规则都没没有获取焦点就执行一个默认操作嘛。用UI系统的话来说就是相当于有一个z轴,Axiom UI在这个z轴的最上层,如果Axiom没有获取焦点就看看白名单有没有获取焦点,这样依次查询下来得到结果嘛

实际上我这里的规则和正常的规则列表的规则一点都不一样,我们这里每个规则的输入都是自己从mixin里插桩获取的,并没有什么规则系统给一个标准的输入内容,规则内部检测输入内容输出结果之类的东西。

3.若解除ImGui的接口封装则需要注意保证未安装Axiom时运行时不会访问到这个类否则会导致异常

这一点我倒是没仔细思考,不过实际运行的时候确实没有出现问题。我刚刚又在调试环境下测试了一下,发现当变量值为null的时候似乎不会加载变量类型指示的类。

@reserveword
Copy link
Owner

还有一个我不太理解的地方,就是我在自己的电脑上测试的时候真的是正常的啊。。就在刚刚我用1.21.1fabric环境测试了modrinth上下载的v5.0.0-1.21+文件,运行起来之后确实是功能运行正常的,没有出现你所说的模组功能不生效的问题。

@reserveword
Copy link
Owner

还有一个我不太理解的地方,就是我在自己的电脑上测试的时候真的是正常的啊。。就在刚刚我用1.21.1fabric环境测试了modrinth上下载的v5.0.0-1.21+模组文件,运行起来之后确实是功能运行正常的,没有出现你所说的模组功能不生效的问题。

甚至我用java17运行时运行1.19.4fabric环境和v5.0.0-1.21+模组文件也能正常加载并且正常工作。

作为参考,我使用的是Windows10自带的微软拼音输入法,或许你可以测试一下是不是输入法的问题?

@LitnhJacuzzi
Copy link
Contributor Author

其实你可以仔细想想你现在的设计原则和没装mod有什么区别......

一个最明显的问题就是没有任何界面显示的时候(也就是正在控制玩家移动操作的时候)肯定是希望始终保持英文状态的,这不也是原来设计方案的初衷吗。另外我使用setConversionStatus方法仅仅是用于自动识别聊天栏的“命令状态”(即是否以“/”开头)并在这个状态变化时手动设置一次当前状态首选的中英文状态(即命令状态为英文,聊天状态为中文)

至于我说的不兼容问题更多的指的是设计原则上的不兼容,在一个完全规范的界面系统里当且仅当焦点发生变化时才会去考虑处理当前输入法的状态,而不是每一个事件刻都去调用一次类似于apply的函数来检测当前的组件状态。不同的焦点系统之间的焦点管理类似于操作系统上切换窗口时的焦点管理方式,存在多于一个焦点系统的时候相当于多一个焦点组件的决定因素(即当前接收键盘输入的焦点系统),焦点系统可以嵌套,每多一层就多一个决定因素(处理输入时被称为事件传递节点)。在一个完全规范的界面实现中,这些因素在发生变化时都会被严格地跟踪并确定好唯一的输入传递路径,例如在窗口系统中可以表示为focusedWindow.focusedWidget,在此模组环境中Axiom的ImGui和MC的界面系统与focusedWindow同级。
我之前有提到过ImGui没有直接的焦点监听回调接口,因此只能每游戏刻去检测一次它的状态,而我之前提到的“外部影响”在MC的窗口能够正确地处理每一个来自操作系统的焦点事件的情况下也是可以完全被监听的,实际上在一个设计完善的焦点系统中这种每刻检测一次状态的函数是完全不需要的

最新的模组文件在我这里确实是没有作用的,这可能是因为特定输入法的问题(我用的是微软拼音),但使用原来的实现方式肯定是任何输入法都不会有问题的

@LitnhJacuzzi
Copy link
Contributor Author

补充说明:我的操作系统环境是Windows11 24H2

@reserveword
Copy link
Owner

其实你可以仔细想想你现在的设计原则和没装mod有什么区别......

为什么我一开始没想到这个问题呢,因为我把切换中英文的快捷键删了,只保留ctrl+space切换输入法开关的快捷键,误触的概率还是挺低的。。。

顺便讲一句,Windows控制输入法的快捷键真是不少啊,有输入法内部自定义的shift,切换不同输入法的ctrl+shift,切换输入法开关的ctrl+space,还有切换不同输入语言的alt+shift。。


eff8696
我回滚了windows输入法切换的功能,现在应该和原来的体验一致了。

@reserveword
Copy link
Owner

其实你可以仔细想想你现在的设计原则和没装mod有什么区别......

一个最明显的问题就是没有任何界面显示的时候(也就是正在控制玩家移动操作的时候)肯定是希望始终保持英文状态的,这不也是原来设计方案的初衷吗。另外我使用setConversionStatus方法仅仅是用于自动识别聊天栏的“命令状态”(即是否以“/”开头)并在这个状态变化时手动设置一次当前状态首选的中英文状态(即命令状态为英文,聊天状态为中文)

至于我说的不兼容问题更多的指的是设计原则上的不兼容,在一个完全规范的界面系统里当且仅当焦点发生变化时才会去考虑处理当前输入法的状态,而不是每一个事件刻都去调用一次类似于apply的函数来检测当前的组件状态。不同的焦点系统之间的焦点管理类似于操作系统上切换窗口时的焦点管理方式,存在多于一个焦点系统的时候相当于多一个焦点组件的决定因素(即当前接收键盘输入的焦点系统),焦点系统可以嵌套,每多一层就多一个决定因素(处理输入时被称为事件传递节点)。在一个完全规范的界面实现中,这些因素在发生变化时都会被严格地跟踪并确定好唯一的输入传递路径,例如在窗口系统中可以表示为focusedWindow.focusedWidget,在此模组环境中Axiom的ImGui和MC的界面系统与focusedWindow同级。 我之前有提到过ImGui没有直接的焦点监听回调接口,因此只能每游戏刻去检测一次它的状态,而我之前提到的“外部影响”在MC的窗口能够正确地处理每一个来自操作系统的焦点事件的情况下也是可以完全被监听的,实际上在一个设计完善的焦点系统中这种每刻检测一次状态的函数是完全不需要的

最新的模组文件在我这里确实是没有作用的,这可能是因为特定输入法的问题(我用的是微软拼音),但使用原来的实现方式肯定是任何输入法都不会有问题的

你讲了这么多不就是想说焦点规则是最大的最高级的最重要的吗,但问题是现在只有焦点规则就是不够啊,其他规则就是需要每tick运行一次来检查是否生效啊,你原来的代码里每个tick也需要分别检查axiom、whitelist、chat和focus啊,我现在不过是把这些东西分别包装成函数罢了,在没有变化的情况下这些东西本来也只会判断几个bool值然后返回啊(注意到IMManager对state有缓存,所以setstate里面其实也只判断一下就返回了)

@LitnhJacuzzi
Copy link
Contributor Author

LitnhJacuzzi commented Jan 6, 2025

为什么我一开始没想到这个问题呢,因为我把切换中英文的快捷键删了,只保留ctrl+space切换输入法开关的快捷键,误触的概率还是挺低的。。。

这个组合默认键位是疾跑跳跃还是一个挺常用的操作吧

我说这些并不是突出焦点的优先级,而是想说明决定因素只有焦点,其他的检测仅仅是为了弥补这个系统里焦点监听的漏洞,我已经说了,一个完善的焦点系统里是不需要每个事件刻都检测组件状态的

聊天状态检测放在刻事件里是因为MC输入框的字符输入处理方法不统一,一一注入过于麻烦才这么写的,说白了还是MC不规范的设计惹的祸

@reserveword
Copy link
Owner

我说这些并不是突出焦点的优先级,而是想说明决定因素只有焦点,其他的检测仅仅是为了弥补这个系统里焦点监听的漏洞,我已经说了,一个完善的焦点系统里是不需要每个事件刻都检测组件状态的

聊天状态检测放在刻事件里是因为MC输入框的字符输入处理方法不统一,一一注入过于麻烦才这么写的,说白了还是MC不规范的设计惹的祸

你再怎么解释也改变不了现在就是需要每tick判断所有规则的事实,以及这一事实在可见的未来只会强化不会回头的事实,所以你的完善的焦点系统终究没法落地。

@LitnhJacuzzi
Copy link
Contributor Author

我更倾向于把这些检测称为补丁而非规则,在设计功能的时候还是要围绕正确的规范来实现,虽然从目前来看这样设计功能上可能是等效的,但不能排除未来有新的需求时这会变为更新解决方案的一种束缚。就现在而言,无论如何都要优先保证模组功能的完整性

@reserveword
Copy link
Owner

如果称为补丁的话更需要保持补丁和主线之间的隔离关系了。当前所有规则(或者说补丁)之间唯一的关联就是他们会在每个tick(以一定的优先级)共同决定输入法是否开启,那么用优先级列表来隔离不同功能的代码不就是自然的选择了吗?

如果未来真的需要发布更复杂的补丁(也就是说,需要在不同优先级上分别执行代码?)我们或许可以通过注册多个不同优先级的规则来实现,但是在那之前我们要维护代码的可读性还是应该使用优先级列表。

最后我想说的是,规则列表并没有损害模组功能的完整性,我现在的这个实现理论上应该和你的实现几乎等价,除了一些小改进比如恢复了screen记录功能recoverScreen之外都是代码迁移、等价代码替换、冗余代码删除之类不影响行为的修改

@LitnhJacuzzi
Copy link
Contributor Author

优先级在这个模组里唯一能合理解释的就是ImGui的独占模式,如果输入处理权可以在ImGui或者未来某个独立的焦点系统与MC的焦点系统之间转移那么这个“优先级”还需要是实时变化的,而同一个焦点系统里的组件由于只存在一个焦点组件更不存在优先级的说法,只是由于焦点的互斥性导致实际上只有一个组件会响应检测,如果这个组件的优先级较低那么之前遍历的组件都是浪费的

与标准规范的传递路径相比优先级规则模式必定是有差异的,即使目前没表现出来也会在未来造成潜移默化的影响

@reserveword
Copy link
Owner

优先级在这个模组里唯一能合理解释的就是ImGui的独占模式,如果输入处理权可以在ImGui或者未来某个独立的焦点系统与MC的焦点系统之间转移那么这个“优先级”还需要是实时变化的,而同一个焦点系统里的组件由于只存在一个焦点组件更不存在优先级的说法,只是由于焦点的互斥性导致实际上只有一个组件会响应检测,如果这个组件的优先级较低那么之前遍历的组件都是浪费的

与标准规范的传递路径相比优先级规则模式必定是有差异的,即使目前没表现出来也会在未来造成潜移默化的影响

基于事件触发的方案与基于轮询的方案本质上确实是不兼容的。如果我们希望做成基于事件触发的方案,那就必须记录当前状态是由哪一条规则决定的:这是因为低优先级规则不能修改高优先级规则定下来的结果,例如minecraft组件不能修改Axiom组件定下来的结果。

但是当前的实现里没有这类代码,所以没法按照基于触发的方案设计系统。如果之后情况有变,需要基于触发方案重写系统也很简单,添加一个公开的优先级状态,存储当前状态优先级,每条规则把当前优先级和自己规则优先级作比较即可。

也就是说,即使用了触发的方案现在的规则结构还是需要的,只是不再轮询执行规则罢了

@LitnhJacuzzi
Copy link
Contributor Author

如果你一定要以“规则”来解释输入法控制的实现方式,那么这个规则就只有一条:当前接收键盘事件的组件及其状态,当输入传递路径发生改变(也就是焦点发生改变时)触发此规则的一次检测,需要注意的是,这个模组的环境下是有办法完全跟踪这种变化的

你讲了这么多不就是想说焦点规则是最大的最高级的最重要的吗,但问题是现在只有焦点规则就是不够啊,其他规则就是需要每tick运行一次来检查是否生效啊,你原来的代码里每个tick也需要分别检查axiom、whitelist、chat和focus啊,我现在不过是把这些东西分别包装成函数罢了,在没有变化的情况下这些东西本来也只会判断几个bool值然后返回啊(注意到IMManager对state有缓存,所以setstate里面其实也只判断一下就返回了)

你所说的axiom、whitelist、chat这几个元素实际上都是输入传输路径上的一个节点:Axiom的ImGui是否捕获键盘输入决定了一级节点是ImGui还是MC界面系统;白名单屏幕和聊天栏本质上是一种焦点组件,也就是二级节点,在一个焦点系统内焦点组件是唯一的,每次监听焦点变化时都会更新二级节点。键盘输入在传递时首先会确定一级节点,然后再在一级节点的系统里确定二级节点得到最终处理输入的组件,这意味着要确定最终处理输入的组件只需实现跟踪这个路径的变化。

我目前没有彻底把功能实现重构成焦点管理的模式,过几天我会在我的分支里实现这一点,届时游戏刻事件里要做的事情只有检测Axiom的状态,未安装Axiom时甚至不再需要订阅这个事件

@reserveword
Copy link
Owner

如果你一定要以“规则”来解释输入法控制的实现方式,那么这个规则就只有一条:当前接收键盘事件的组件及其状态,当输入传递路径发生改变(也就是焦点发生改变时)触发此规则的一次检测,需要注意的是,这个模组的环境下是有办法完全跟踪这种变化的

说得好,那么请问:判断一般文本框获得焦点的逻辑和判断白名单screen获得焦点的逻辑是一样的吗?和判断聊天栏呢?说到底不还是得为不同情况分别编写代码处理吗?

我目前没有彻底把功能实现重构成焦点管理的模式,过几天我会在我的分支里实现这一点,届时游戏刻事件里要做的事情只有检测Axiom的状态,未安装Axiom时甚至不再需要订阅这个事件

那你先写好,到时候我再合入就好。如果你说的焦点管理模式就只是类似于”把screen变动时更新字段值改成screen变动时更新state值、并调用sync“这样的设计,你就看好我怎么把你的pr改成四条规则吧

@LitnhJacuzzi
Copy link
Contributor Author

你如果一定要坚持自己的实现方式,那么未来你的主线里出现任何我这里不会出现的问题我将不再受理,所有的更新内容也会在我的分支里独立实现。我自己的工作也很忙,没那么多功夫为了一些本不应该出现的问题操心

@reserveword
Copy link
Owner

你如果一定要坚持自己的实现方式,那么未来你的主线里出现任何我这里不会出现的问题我将不再受理,所有的更新内容也会在我的分支里独立实现。我自己的工作也很忙,没那么多功夫为了一些本不应该出现的问题操心

这边建议您用PR来说话。当前我的写法和你的写法没有功能上的区别。

@LitnhJacuzzi
Copy link
Contributor Author

你发布的最新版(5.0.1)我用了不到十分钟就出现了漏洞:如果在命令状态下输入法以中文状态或在聊天状态下输入法以英文状态退出聊天界面,下一次打开聊天栏自动切换中英文状态必定失效,且如果不改变命令状态就关闭聊天界面则一直不会解除

典型触发场景:私聊玩家 - /tell <玩家名称> <中文聊天内容>,再次使用指令时初始状态仍为中文

此问题在我这里从未出现过

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants