第四面墙破还是不破,eva,崩2,龙背,尼尔,和崩3

(叠甲,崩2和龙背3的剧情是云的,尼尔机械纪元是玩过的两轮(指从头清零玩),以下内容含有上面这些作品的剧透)

啰嗦的视频版:https://b23.tv/GC8VvS5

众所周知,崩坏系列一直就有着大型eva同人作品的称号。虽然精神内核上崩坏的故事可以说和eva没有那么多的相似之处,但在表达方式上制作组往往也按不住自己想要致敬的手。

从老剧场版一转实景拍摄观众席,到新剧场版父子打架打出摄影棚到最后车站又实景,乃至贯穿最后一部的“再见了,所有的福音战士”,创作者们有时候是想要通过更直接的方式,仿佛喊话一般把自己的感情传递给观众。尽管在所谓的“严谨性”方面有时会有相应的欠缺。

而崩坏系列也不是第一次在主线中这样直接和玩家交流了。早在崩坏2逐火之蛾DLC的结局,就进行过一次由玩家意志上线代打的剧情。所以在崩坏3中再见到一次直接的对话,倒也并没有让我非常惊讶。

而在表现形式上,崩坏3这次结局的部分则有些类似尼尔机械纪元和龙背3结合。龙背3的观测者有着和大爱酱类似的身份,而她们都在观测的同时对这段“故事”产生了感情。而核心密钥留言和集合玩家之力破盾,则又颇有机械纪元e结局给其他玩家寄语,“借用”其他玩家存档来代替生命损耗。

当然,表现形式的类似并不代表感情的底色是一样的。龙背和尼尔的监督横尾太郎自己也是eva的粉丝。(这下万物起源eva了,笑)在我看来他的剧情倒是很有eva那种闹别扭的扭曲感情一面甚至还要更多,但崩坏系列从来就是一个充满希望的故事。如果说尼尔机械纪元和龙背3的最终结局的解锁方式都有一种外力“扭转绝望”的意味(观测者/pod觉醒),但表现出来都是含有很强的和玩家对话的趋势。龙背可能还不那么直接,尼尔e结局中的连续选项则可以说是直接向玩家提问了。

如果说龙背的“观测者”还有那么一些“机械”觉醒人性的意味安排存在的话,扭转尼尔e结局的和制作组名单打飞机就是完全跳脱于设定之外的演出了。

在一次横尾太郎访谈中,关于打破第四面墙的问题横尾太郎是这样回答的。

——在《尼尔机械军团》中有不少Meta要素,游戏中的角色会“打破第四面墙”,好像是制作者通过角色直接和玩家做交流,请问横尾先生是出于什么想法做了这样的设计?

横尾:我想电子游戏有很多的可能性,不过现在的很多游戏都有公式化的倾向。而脱离这种“公式化”所做出的游戏,会给人一种新鲜激动的感觉,就像我小时候所感受的那样。所以我会不断去探索电子游戏新的可能性。

崩坏3的制作组可能也有类似的想法。

回到崩坏3的结局上来,崩坏3的核心密钥设置的部分竟然是之前的“活动剧情”独占的。这意味着这段演出实质上是针对崩坏3老玩家的礼物。毕竟崩坏3也是一个已经运营了超过6年的“高龄”游戏了。我愿意相信制作组就是真的想在这段演出中和玩家“舰长”直白的感谢。

在核心密钥之后立刻就是开休伯利安的剧情,不如说正式让我们当了名副其实的舰长。

玩家集合帮忙破盾,除了给玩家参与感之外,也暗合了集合全人类之力的主题。

诚然,这种演出方式从另一个角度讲,也有“破坏故事”,或者所谓的“机械降神”。即使尼尔的e结局,也有评价为“机械降神”的看法。

但这其中也有我认为游戏作为第九艺术的魅力所在。游戏是一种交互的艺术,是有别于其他艺术形式的重要特征。在某些剧情和游玩割裂的地方,例如玩家实际打趴了敌人cg却一转被吊打,往往会有这样的吐槽“别拿游戏性和设定混为一谈啦”。

而崩坏3这次的演出,在我看来反而不是这种割裂的例子。如果你一路游玩下来,只要你真心喜欢这个游戏,认同它一直一来的内核,相信不难注意到玩家在结局希望看到一个“更美好”的世界的愿望和游戏中的人物是同一的。这时我们参与进去,看到当初的寄语,然后借由“核心密钥”操作休伯利安正是对这种感情的回应。

总结下来,我对于这段演出的情绪是认同的。而且制作组也努力掌握好了不让大爱酱承担关键转折的点避免真的变成“机械降神”。应当把它当作对玩家的彩蛋礼物,而不是剧情核心。

最后用我自己的寄语结束这么多的唠叨吧。

我永远喜欢崩坏3!

Posted in 日志 | Tagged | 1 Comment

A comprehensive guide about using Fcitx 5 on wayland

The term fragmentation is IMHO the best word to describe wayland. And that’s the reason why this page exists. While traditional “three lines” (XMODIFIERS, GTK_IM_MODULE, QT_IM_MODULE) also mostly work under wayland, but there is some other way to setup and special care needed to make all application works.

Hopefully I covered most of things in this wiki page: https://fcitx-im.org/wiki/Using_Fcitx_5_on_Wayland

Also, you are always welcome to contribute to fcitx’s wiki. https://fcitx-im.org/wiki/Contribute_to_this_Wiki

Posted in fcitx development | Leave a comment

How to make your application support Input method under Linux

As an Linux application developer, one might not aware that there could be certain effort required to support Input Method (or Input Method Editor, usually referred as IME) under Linux.

What is input method and why should I care about it?

Even if you are not aware, you are probably already using it in daily life. For example, the virtual keyboard on your smart phone is a form of input method. You may noticed that the virtual keyboard allows you to type something, and gives you a list of words based on what you already partially typed. That is a very simple use case of input method. But for CJKV (Chinese, Japanese, Korean, Vietnamese) users, Input method is necessary for them to type their own language properly. Basically imagine this: you only have 26 English key on the keyboard, how could you type thousands of different Chinese characters by a physical keyboard with only limited keys? The answers, using a mapping that maps a sequence of key into certain characters. In order to make it easy to memorize, usually such mapping is similar to what is called Transliteration , or directly use an existing Romanization system.

For example, the most popular way for typing Chinese is Hanyu Pinyin.

In the screenshot above, user just type “d e s h i j i e”, and the input method gives a list of candidates. Modern Input method always tries to be smarter to predict the most possible word that the user wants to type. And then, user may use digit key to select the candidate either fully or partially.

What do I need to do to support Input method?

The state of art of input method on Linux are all server-client based frameworks. The client is your application, and the server is the input method server. Usually, there is also a third daemon process that works as a broker to transfer the message between the application and the input method server.

1. Which GUI toolkit to use?

Gtk & Qt

If you are using Gtk, Qt, there is a good news for you. There is usually nothing you need to do to support input method. Those Gtk toolkit provides a generic abstraction and sometimes even an extensible plugin system (Gtk/Qt case) behind to hide all the complexity for the communication between input method server and application.

The built-in widget provided by Gtk or Qt already handles everything need for input method. Unless you are implementing your own fully custom widget, you do not need to use any input method API. If you need your custom widget, which sometimes happens, you can also use the API provided by the toolkit to implement it.

Here are some pointers to the toolkit API:

Gtk: gtk_im_multicontext_new GtkIMContext

Qt: https://doc.qt.io/qt-6/qinputmethod.html https://doc.qt.io/qt-6/qinputmethodevent.html

The best documentation about how to use those API is the built-in widget implementation.

SDL & winit

If you are using SDL, or rust’s winit, which does have some sort of input method support, but lack of built-in widget (There might be third-party library based on them, which I have no knowledge of), you will need to refer to their IME API to do some manual work, or their demos.

Refer to their offical documentation and examples for the reference:

https://wiki.libsdl.org/SDL2/Tutorials-TextInput

https://github.com/libsdl-org/SDL/blob/main/test/testime.c

https://github.com/rust-windowing/winit/blob/master/examples/ime.rs

Xlib & XCB

Xlib has built-in XIM protocol support, which you may access via Xlib APIs. I found a good article about how to add input method support with Xlib at:

https://tedyin.com/posts/a-brief-intro-to-linux-input-method-framework/

As for XCB, you will need to use a third-party library. I wrote one for XCB for both server and client side XIM. If you need a demo of it, you can find one at:

https://github.com/fcitx/xcb-imdkit/blob/master/test/client_demo.c

Someone also wrote a rust binding for it, which is used by wezterm in real world project. Some demo code can be found at:

https://github.com/H-M-H/xcb-imdkit-rs/tree/master/examples

wayland-client

As for writing a native wayland application from scratch with wayland-client, then you will want to pick the client side input method protocol first. The only common well supported (GNOME, KWin, wlroots, etc, but not weston, just FYI) one is:

https://wayland.app/protocols/text-input-unstable-v3

2. How to write one with the APIs above?

If you use a toolkit with widget that can already support input method well, you can skip this and call it a day. But if you need to use low level interaction with input method, or just interested in how this works, you may continue to read. Usually it involves following steps:

  1. Create a connection to input method service.
  2. Tell input method, you want to communicate with it.
  3. Keyboard event being forwarded to input method
  4. input method decide how key event is handled.
  5. Receives input method event that carries text that you need to show, or commit to the application.
  6. Tell input method you are done with text input
  7. Close the connection when your application ends, or the relevant widget destructs.

The 1st step sometimes contains two steps, a. create connection. b. create a server side object that represent a micro focus of your application. Usually, this is referred as “Input Context”. The toolkit may hide the these complexity with their own API.

Take Xlib case as an example:

  1. Create the connection: XOpenIM
  2. Create the input context: XCreateIC
  3. Tell input method your application wants to use text input with input method: XSetICFocus
  4. Forward keyevent to input method: XFilterEvent
  5. Get committed text with XLookupString
  6. When your widget/window lost focus, XUnsetICFocus
  7. Clean up: XDestroyIC, XCloseIM.

Take wayland-client + text-input-v3 as an example

  1. Get global singleton object from registry: zwp_text_input_manager_v3
  2. Call zwp_text_input_manager_v3.get_text_input
  3. Call zwp_text_input_v3.enable
  4. Key event is forward to input method by compositor, nothing related to keyboard event need to be done on client side.
  5. Get committed text zwp_text_input_v3.commit_string
  6. Call zwp_text_input_v3.disable
  7. Destroy relevant wayland proxy object.

And always, read the example provided by the toolkit to get a better idea.

3. Some other concepts except commit the text

Support input method is not only about forwarding key event and get text from input method. There are some more interaction required between application and input method that is important to give better user experience.

Preedit

Preedit is a piece of text that is display by application that represents the composing state. See the screenshot at the beginning of this article, the “underline” text is the “preedit”. Preedit contains the text and optionally some formatting information to show some rich information.

Surrounding Text

Surrounding text is an optional information that application can provide to input method. It contains text around the cursor, where the cursor and user selection is. Input method may use those information to provide better prediction. For example, if your text box has “I love |” ( | is the cursor). With surrounding text, input method will know that there is already “I love ” in the box and may predict your next word as “you” so you don’t need to type “y-o-u” but just select from the prediciton.

Surrounding text is not supported by XIM. Also, not all application can provide valid surrounding text information, for example terminal app.

Reporting cursor position on the window

Many input method engine needs to show a popup window to display some information. In order to allow input method place the window just at the position of the cursor (blinking one), application will need to let input method know where the cursor is.

Notify the state change that happens on the application side

For example, even if user is in the middle of composing something, they may still choose to use mouse click another place in the text box, or the text content is changed programmatically by app’s own logic. When such things happens, application may need to notify that the state need a “reset”. Usually this is also called “reset” in the relevant API.

Posted in Linux | Tagged , , , , | 3 Comments

一趟神奇的 Debian 环境变量之旅

更正:startx 进行 unset DBUS_SESSION_BUS_ADDRESS 的行为是来自上游,而不是来自 debian ( https://gitlab.freedesktop.org/xorg/app/xinit/-/issues/9 ),而过去这样做的理由应该主要是让 startx 和已有的 session bus 相互隔离吧,但是在 systemd 的 user session bus 成为主流的现在,这个行为反而会导致问题。arch 只是获得这个修复更早,并不是 debian 自己的 patch 增加的这一行。

这是一件一个月之前的事情,有一个人来到 fcitx 的 telegram 群说他在 debian lxqt 不能在 chromium 下输入。在他贴了一下 chromium 在终端输出的结果之后,事情开始变得奇怪了起来。

$ [12752:12787:1013/110502.625383:ERROR:bus.cc(399)] Failed to connect to the bus: Could not parse server address: Unknown address type (examples of valid types are "tcp" and on UNIX "unix")
[12752:12787:1013/110502.625545:ERROR:bus.cc(399)] Failed to connect to the bus: Could not parse server address: Unknown address type (examples of valid types are "tcp" and on UNIX "unix")

意思是 chromium 连接到 dbus 失败,但这个时代,除了一些奇葩的反 systemd 发行版,正常只要你用 systemd ,就应该能正确设置 dbus 的环境变量。光单单为了这一点,我就得感谢 systemd。早在 11 年前我的一篇博客曾经就写道不用主流桌面不用 display manager 而导致了 dbus 设置不对而出现的奇葩问题。但这个时代我还真没有见过用了 systemd 却还没有自动设置正确 dbus 的情况。于是接下来的事情就探入了我以前从未了解过的兔子洞中。

首先,systemd 帮助你设置 DBUS_SESSION_BUS_ADDRESS 环境变量的原理是一个 pam 模块,所谓的 pam 就是 linux 在你登录系统的时候会自动调用的一系列模块,其中例如就有 pam_env 可以读取 /etc/environment 来设置环境变量,pam_kwalletd5 来把你输入的密码传递给 kwalletd 直接进行 kwallet 解锁等等。

而 pam_systemd 正是承担了设置一系列标准 XDG 和 DBUS 环境变量的任务。所以才有了前面说的,只要你用了 systemd,就不应该出现 dbus 设置问题。而好巧不巧,他用的发行版并非正常的 debian 发行版,而是一个 debian 的衍生发行版 omv。他的默认安装正好没有包含 libpam-systemd。这种事谁能想到?但是接下来我在虚拟机中安装 lxqt 之后,却发现 libpam-systemd 作为某种依赖(猜测是推荐依赖)被安装上了。

这问题又回到了原点,接下来我们发现,他是使用 startx 来启动系统的,而他在 startx 之前,环境变量中是有 DBUS_SESSON_BUS_ADDRESS 存在的。也就是说,在 startx 之后的某个过程中,DBUS_SESSION_BUS_ADDRESS 被什么东西 unset 了!你要让我猜,我恐怕想破脑袋都无法想到到底是什么东西 unset 了这个环境变量。而我直接 startx ,根据 startx 要启动 /etc/alternatives/x-session-manager 我也可以直接进入 lxqt,而这样启动的 lxqt 环境完全正常。

接下来只有亲自实践一条路了,然后我模仿和他一模一样的配置,把 exec startlxqt 写到了 ~/.xinitrc 中,结果发现确实之后就没有 DBUS_SESSON_BUS_ADDRESS 了。此时我是完全没有头绪到底是为什么,抱着随便试一试的心情,我开始对整个磁盘上的文件进行 grep DBUS_SESSON_BUS_ADDRESS 。结果发现在 startx 的第一行赫然就写着 unset DBUS_SESSION_BUS_ADDRESS?!WTF?

看看我自己系统上的 startx,并没有这样的内容,而实际上这是最近在上游才修改的 https://gitlab.freedesktop.org/xorg/app/xinit/-/issues/9 ,在读了许久 debian startx 相关的脚本在之后,这里将 debian 机制写在这里:

1、首先,startx 之后 debian 会查找 ~/.xinitrc 或者系统级别的 /etc/X11/xinit/xinitrc,而系统级的 /etc/X11/xinit/xinitrc 里面的内容是 . /etc/X11/Xsession,也就是执行 debian 自己独有的 /etc/X11/Xsession 。而这个 /etc/X11/Xsession 干的事情概括起来,就是按顺序加载 /etc/X11/Xsession.d 下面的脚本并启动 x-session-manager 指向的东西。而它在 startx 里 unset DBUS_SESSION_BUS_ADDRESS 之后,会由 /etc/X11/Xsession.d 中的某个脚本将这个变量重新设置回来,从而达成和其他系统类似的效果。而当你使用自己的 ~/.xinitrc 的时候,/etc/X11/Xsession.d 这一系列的脚本都会被跳过,从而导致 DBUS_SESSION_BUS_ADDRESS 不被设置。

所以在 debian 下如果你想要使用 startx ,最好的办法是不要用 ~/.xinitrc ,而用 debian 专有的 ~/.xsession 代替,从而让 debian 自己的 /etc/X11/xinit/xinitrc 加载 /etc/X11/Xsession.d 下许多环境设置再进入桌面。

而如果你在其他的发行版上(如 Arch),你则不能使用 ~/.xsession ,必须用 ~/.xinitrc ,因为 ~/.xsession 是 /etc/X11/Xsession 这一整套 debian 独有的脚本负责的。

startx 的上游这个奇怪的 unset 在过去大家总要手动执行 dbus-launch 或者基于 x11 auto launch 的时代有一定的意义,但是在 dbus 改由 systemd user 启动的现在,已经不再有意义了。debian 相当于是有自己的一套机制来恢复这个变量的值,但是只能在 .xsession 中起作用, ~/.xinitrc 并不能享受到。

当我最终发现原因的时候,我整个人是无语的。这是一个你不到 debian 系统里看看就根本发现不了的问题。早年 Linux 各个发行版各自为战搞了很多自己一套独有的东西,创建启动镜像我能叫出来的就至少有三套 dracut,mkinitramfs,mkinitcpio。现在 systemd 好歹在把大家强行统一这件事情上推着走了很长的路。但也有这种历史残留会留下各种各样的不一致行为。因为你问我好用不好用,那当然是挺好的,特别是debian 自己打包的许许多多的工具实际上是依赖这一整套行为的。所以你就算让它今天删掉变成 vanilla 的版本,也并不是一件简单的事情。

这也可以说回我用 arch 作为系统的一个原因,就是系统上的包都是接近 vanilla 的状态(即上游的原始代码没有改动),能够最大程度获得和开发者一致的使用体验。

Posted in Linux | Tagged , , , | 3 Comments

Get event order right (Try 2!)

TL;DR: this is not considered as a user facing change.

In a previous post, we discussed the issue between the input method event order and the blocking dbus call. To put it simple, input method may generate multiple different outcomes from a single key press, such as committing text, set preedit, etc. The key press comes as a Inter-process communication(IPC) from an application to Fcitx.

If this IPC is blocking, then the event can only arrive after the call is done. Previously, we tried to always use async call to ensure the event happens before the reply is delivered to application first. This can’t be used for Gtk 4 GtkIMContext anymore. Gtk 4 hides too much API comparing to Gtk3, which prevents us from doing a lot of things, for example, re-inject the key event into the application.

In the old async mode, the key event will always be filtered by Fcitx IM Context, then re-inject into the application when the result of event handling returns. Upon the result is received, Fcitx IM Context will copy the GdkKeyEvent back into the application with a special flag on the modifier, to prevent it from being handled by Fcitx IM Context again.

In Gtk 4, there is no API to create a synthetic key event (which is problematic for some other features that Fcitx supports, but we will not discuss that here), which means we will need to implement using the synchronous mode anyway.

Well, not really “must”, because I do find some API to allow a hacky asynchronous implementation, by memorizing the pointer address of GdkEvent and use gdk_put_event to reinject the event. Though that doesn’t work for chromium code because it doesn’t use gdk for event handling.

So what we can do here? The answer is, we create a new version of ProcessKeyEvent API, ProcessKeyEventBatch.

In the old synchronous mode, the root cause of wrong event order is the event sending from input method can only be handled “after” the synchronous ends, which is not we want to see.

In the new ProcessKeyEventBatch, what we do is we do not send the event from input method to application immediately. We block the sending procedure on the input method side until the reply happens. When the reply is finally being sent, Fcitx will put all the events that need to be handled by application in the reply.

Say, application want to commit some text before the key event is handled by application. After commitString() is called on the input method side, the CommitString dbus signal doesn’t happen in the new mode, instead, we wait and put them together in the return value of “ProcessKeyEventBatch”. Upon receiving the reply, the FcitxIMContext first decodes the reply to see if there is anything piggybacked in the same reply, and handles them first. This will make the event order consistent on both of the input method side and the application side.

Posted in fcitx development | Tagged , , | Leave a comment