Termux-x11、Xserver-Compositor和OpenFDE窗口融合
- 1 背景介绍
- 2 什么是Termux和Termux-x11?
- 2.1 在Termux-x11上运行X程序
- 2.1.1 实验步骤
- 2.1.2 Termux-x11原理浅析
- 2.1.3 Action 1 : 改造termux/termux-x11
- 2.1 在Termux-x11上运行X程序
- 3 怎么使用Compositor实现窗口融合?
- 3.1 Compositor源码说明
- 3.2 窗口融合原理
- 3.2.1 1. 显示重定向
- 3.2.2 Action 2 : 重定向后buffer的生命周期管理或者说X程序生命周期的管理
- 3.2.3 2. 事件重定向
- 3.2.4 Action 3:事件重定向的坐标转换,窗口ConfigreNotify
- 3.2.5 3. 安卓端实现
- 3.2.6 Action 4 :需要在安卓端研究的工作:
- 3.3 窗口分离实验步骤
- 4 Action 5:探究另一种可能的实现方式:Xserver多screen
- 5 未来需要做的事 ACTION
背景介绍
请先读本段!请先读本段!请先读本段!
本文是在OpenFDE上实现linux窗口和Android窗口融合的一种方案预研和设想,因为方案有些复杂,这段文字作为一个前情提要,描述一下背景知识,不然直接开始容易让人懵逼。
首先,需求是这样的,因为OpenFDE是运行在linux上的Android桌面,那如果在OpenFDE上运行一个xserver,是不是linux程序就可以直接在OpenFDE上运行了,并且效果跟原生运行在linux一样。在Android上已经有一些xserver的实现了,比如google的最简版Java xserver,还有Xsever-SDL,经过各种研究对比,termux-x11是实现最完整,工程化也最好的项目。
Xserver-SDL:https://github.com/pelya/xserver-xsdl 是在Android上的直接移植,但是扩展支持很少,性能,工程化都不太好
Google xserver:https://github.com/pelya/xserver-xsdl 在google基础版上经过开发者完善了,但是功能还是很基础,兼容性差
其次,仅仅在Android上运行一个Xserver,在一个Android窗口里模拟一个Xserver屏幕,那效果跟任何平台都没有区别了,因为linux就运行在本机,我们团队提出的需求是,linux的单个程序就像Android的单个程序一样运行,就是窗口融合的概念。简单理解就是每个linux窗口对应一个activity,在Xserver-SDL的github,笔者也看到别人有这样的发散思维https://github.com/pelya/xserver-xsdl/issues/65 。
经过相当长时间的文档熟悉,实验验证,方案探索(当然本方案也还在探索),目标似乎是可以实现的了,下文是大致记录可行性研究过程和思路描述,在具体的实现过程中,这些思路也会有修正。每一个大致的环节,后续都应该会有各种设计,验证,调整的代码和文档输出。
什么是Termux和Termux-x11?
Termux 是一个 Android 终端应用程序和 Linux 环境。直观的来看,Termux 是运行在 Android 上的 terminal,不需要root,运行于内部存储(不在SD卡上),目录位于本应用的file目录,配置了相应的环境变量,包安装命令是pkg install xxx,实际是一个封装APT的脚本。
环境变量配置:
安装库路径:/data/data/com.termux/files/usr/lib
当你使用termux作为终端来安装和运行程序时,应该是从代码仓库编译https://github.com/termux/termux-packages ,并发布到apt源的程序。
Termux-packages该项目包含用于构建 Termux Android 应用程序包的脚本和补丁。简单对比了一下补丁文件,项目构建的方式主要是将linux的编译选项改为Android,如果原本项目就支持Android编译,即可以最简单的直接编译出程序,否则就要先安装在Android上本来不存在的依赖。
Termux-X11 是一个成熟的 X 服务器。它是使用 Android NDK 构建的,并针对与 Termux 一起使用进行了优化。
源码地址:https://github.com/termux/termux-x11
经过学习源码,termux-x11支持了大部分xserver扩展,运行效率好,项目工程化好,可以直接编译运行。
在Termux-x11上运行X程序
实验步骤
安装termux
编译运行https://github.com/termux/termux-app ,
或者在 F-droid https://f-droid.org/en/packages/com.termux/ 下载APK。
安装termux-x11命令行
在termux终端执行如下命令,即相当于在termux环境安装了termux-x11库或者脚本命令,真正的运行程序需要安装termux-x11 APK。
pkg install x11-repo //x11依赖
pkg install termux-x11 //termux-x11命令行
pkg install termux-x11-nightly //termux-x11命令行
编译安装termux-x11 APK
编译环境必须是linux,并且需要安装以下依赖。
sudo apt update && sudo apt install -y libarchive-tools binutils-aarch64-linux-gnu binutils-arm-linux-gnueabihf gcc bison
将https://github.com/termux/termux-x11 导入 Android studio,编译安装到FDE。
编译配置可以参考workflow:
从编译产出看,应该是相关依赖也一并生成了,或者可以取代pkg从apt源安装。
files: |
./app/build/outputs/apk/debug/app-*-debug.apk
./app/build/outputs/apk/debug/shell-loader-nightly.apk
./app/build/outputs/apk/debug/termux-x11-*-all.deb
./app/build/outputs/apk/debug/termux-x11-*-any.pkg.tar.xz
运行termux-x11即x11 server,在termux执行
在linux端启动X程序连接
效果图:
Termux-x11原理浅析
从以上的执行过程来看,启动x11 server(即termux-x11)的方式是在termux终端运行termux-x11 :0命令。
Termux-x11是通过pkg安装的脚本,直接启动x11 server的代码是在脚本中。/system/bin/app_process
是 Android 系统上的应用程序启动器,它负责启动指定的 Java 类。
这是com.termux.x11.Loader的源码,
实际是使用当前环境变量,通过反射启动上面这个类的main函数,相当于执行了一个java程序,CmdEntryPoint是termu-x11源码中的类,是x11 server真正启动的地方。
CmdEntryPoint又是一个AIDL的stub,意味着他作为一个AIDL service启动,可以接收原termux-x11 APK的接口函数。当你手动从桌面点击termux-x11的图标的时候,会启动activity传入显示用的SurfaceView。
笔者理解Termux-x11有两条运行线:
termux-->app_process-->com.termux.x11.CmdEntryPoint-->startserver
termux-x11-->SurfaceView-->com.termux.x11.ICmdEntryInterface.windowChanged-->xserver
两条线的最后代码都是在termux-x11,并且两条线不互相干扰,server启动后不置入SurfaceView,不显示图像,server也可以正常在后台运行。
Action 1 : 改造termux/termux-x11
简单描述这个问题:将termux、termux-x11安装启动xserver的过程,做到用户无感知,交互正常,不需要pkg安装,也不需要命令行操作。
(?笔者认为的关键是,解决环境变量,库依赖的问题)
可能的最终形态:
改成普通安卓service或者系统安卓service,运行在后台;
提供一个融合应用的入口,类似VNC的《Fusion Linux Application》,用户启动后再拉起server,列出X程序,点击X程序再启动linux端的连接。
怎么使用Compositor实现窗口融合?
合成器扩展Compositor,又叫混成器,一句话形容,是用来将不同的X程序的显示内容合成成最终图像的扩展模块。既然是窗口管理程序,使用的肯定也是X协议的窗口管理协议,ICCCM/EWMH,参见 https://tronche.com/gui/x/icccm/ 。他的主要函数是从窗口获取图像,可以用来实现各种窗口特效。
使用Compositor扩展后,xserver服务端和compositor端都有相应的回调函数拿到想要的窗口的图像。
Compositor源码说明
[Composite extension protocol specification] https://cgit.freedesktop.org/xorg/proto/compositeproto/tree/compositeproto.txt
[合成器扩展功能练习] http://www.talisman.org/~erlkonig/misc/x11-composite-tutorial/
从composite源码来看关键函数是:
合成器连接上server后,会对所有窗口重定向,在重定向的回调函数中就可以通过NameWindowPixmap获取对应程序的图像,其本质就是GetWindowPixmap,不过需要在重定向窗口之后执行。
合成器的一种简单使用是实现X程序截图,很多X程序在最小化之后,需要将隐藏了的图像取帧做为提示图,如果使用合成器扩展,就是简单两步,重定向窗口,保存图像:
窗口融合原理
FDE上要实现的窗口融合效果是Linux的每个X程序跟安卓的每个应用显示效果一致,使自己的程序独有窗口。当我们能够将每个X程序的图像从xserver的合成器中分离出来的时候,方案的前置条件就成立了。当然具体实现还要从以下几个方面着手。
1. 显示重定向
显示重定向是最容易理解的,就是把X程序对应用窗口的显示内容转到另一个单独的buffer中,FDE再把这个buffer拿去安卓中做显示,即是output的处理。
与compositor配合使用的一个扩展是damage,在重定向后,damage注册在不同的window或者screen上,就可以实现监听哪些区域有变化,再选择更新图像。在termux-x11中的实现是定时器检查damage状况,但是却不是把damage注册在对应的window上,这个是可以优化的。
Action 2 : 重定向后buffer的生命周期管理或者说X程序生命周期的管理
可能要拿到以下确切数据,来构建安卓窗口:
connection建立过程,window创建过程,window的详细参数;
window分类,哪些需要新窗口显示,哪些在内部处理;
优化damage机制;
....
2. 事件重定向
Compositor扩展并没有处理事件重定向,意思就是说他不重定向输入事件。他会将OverlayWindow 的所有输入直接传到下层窗口,相当于什么也没做。
那如果窗口重定向后,显示到一个离屏的区域,用户在新渲染的安卓窗口上操作,input事件(鼠标键盘事件)怎么发生在正确的位置呢?启用合成器,只是加了一个中间过程,最终还是会把所有程序的图像合成显示出来,所以可以大胆设想,利用这个原本的input坐标系,简分为三类情况来处理:
这里用一个虚框来表示原本的显示效果,即原本不做任何操作,xserver会这样显示X程序,实框表示真正显示的效果,安卓会这样显示X程序。
操作在前台的程序firefox,如果需要发送input事件,只需要把坐标转换成正确的坐标发送事件即可;即 android window mouse.x .y -> xserver screen.x .y
操作未在前台的程序wps, 如果是fling或者hover操作,根据damage通知更新即加,如果有点击操作,就是在安卓窗口获取到焦点的时候将WPS在Xserver的位置也切换到前台,剩下的操作与第1点相同。
平移和缩放窗口,平移是在安卓窗口上的操作,对X程序的显示其实不产生影响,甚至可以不做处理,因为重定向之后,即使有堆叠也可以拿完全显示的X程序图像。缩放在安卓上几乎不需要太多改动,但是在xserver却要重绘,resize等操作,是一个需要把操作指令转换成xserver函数的工作。
Action 3:事件重定向的坐标转换,窗口ConfigreNotify
缩放时处理Randr;
坐标系转换;
快捷键捕获;
文本输入移植ibus?;
...
3. 安卓端实现
相较于Xserver的改动,安卓的实现简单,在VNC上的处理都可以直接移植,遗留有一个activity多栈未解决,也不是马上必须要解决的。
关于input,VNC与xorg-xserver对输入的处理是不是完全一致也是需要验证的。
关于output,所有的X程序的图像buffer都有两份,虽然合成器合成后的那个图像并不会做显示,但是也占用了内存,需要评估最终效果。
Action 4 :需要在安卓端研究的工作:
复杂输入事件需要验证,比如组合事件是否能直接生效;
显示数据使用翻倍,内存有没有问题;
资源配置文件的完善(字体,字符,键盘布局文件等);
DecorcaptionView跟X程序操作按钮的替换;
...
窗口分离实验步骤
以上是原理方面的设想,当要做工程实现的时候,最好是在现有项目做一定的验证,而且只对关键部分做验证,显然显示重定向是决定方案是否可行的关键。但是之前要解决另一个问题,Android多SurfaceView渲染的问题。
Android多SurfaceView渲染
现在大部分安卓项目在使用opengl渲染的时候已经都学会自己配置EGL,而不是直接使用GlSurfaceView,好处是可以自己管理EGL环境,可以处理所有资源的申请和释放,不再跟view的生命周期绑定,不再使用原生的render,自己组织gl指令,满足各种特制化需求。
SurfaceView + EGL环境渲染程序的一般流程如下。
配置资源:
绘制图像:
具体的绘制逻辑在:
尝试新增一个SurfaceView,依次绑定context,当然这个_surface1也是eglCreateWindowSurface生成的,验证两个SurfaceView能够在同个EGL环境中渲染。
效果图:
Termux-x11不同SurfaceView显示窗口buffer
验证成功后,开始使用多SurfaceView来渲染termux-x11上的多纹理buffer。
改动如下:
传入多SurfaceView
初始化surface
在合成扩展回调中获取新窗口ID
绑定otherdisplay纹理
在demage回调的时候,把两个surface都渲染一遍
调试效果如下,这里的弹窗口是选项弹窗,未选特定窗口,不如看视频
Action 5:探究另一种可能的实现方式:Xserver多screen
每个X程序独占一个screen,所有X程序的input,output不产生关系,唯一需要处理焦点切换。
未来需要做的事 ACTION
改造termux/termux-x11;
重定向后buffer的生命周期管理或者说X程序生命周期的管理;
事件重定向的坐标转换,窗口ConfigreNotify;
复杂输入事件需要验证,比如组合事件是否能直接生效;
探究另一种可能的实现方式:Xserver多screen。