目录 | ||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
背景介绍
请先读本段!请先读本段!请先读本段!
本文是在OpenFDE上实现linux窗口和Android窗口融合的一种方案预研和设想,因为方案有些复杂,这段文字作为一个前情提要,描述一下背景知识,不然直接开始容易让人懵逼。
...
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 一起使用进行了优化。
...
代码块 |
---|
export DISPLAY=:0 firefox wps ... //启动合成器、窗口管理器 xfwm4 i3 |
效果图:
...
Termux-x11原理浅析
从以上的执行过程来看,启动x11 server(即termux-x11)的方式是在termux终端运行termux-x11 :0命令。
...
代码块 |
---|
public class CmdEntryPoint extends ICmdEntryInterface.Stub { public static final String ACTION_START = "com.termux.x11.CmdEntryPoint.ACTION_START"; public static final int PORT = 7892; public static final byte[] MAGIC = "0xDEADBEEF".getBytes(); private static final Handler handler; public static Context ctx; ... |
笔者理解Termux-x11有两条运行线:
termux-->app_process-->com.termux.x11.CmdEntryPoint-->startserver
termux-x11-->SurfaceView-->com.termux.x11.ICmdEntryInterface.windowChanged-->xserver
两条线的最后代码都是在termux-x11,并且两条线不互相干扰,server启动后不置入SurfaceView,不显示图像,server也可以正常在后台运行。
...
简单描述这个问题:将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/
...
代码块 | ||
---|---|---|
| ||
#include <stdlib.h> #include <stdio.h> #include <unistd.h> #include <string.h> #include <libpng16/png.h> #include <X11/X.h> #include <X11/Xlib.h> #include <X11/Xutil.h> #include <X11/extensions/Xcomposite.h> #include <X11/extensions/Xrender.h> //Compile hint: gcc -shared -O3 -lX11 -fPIC -Wl,-soname,prtscn -o prtscn.so prtscn.c typedef int bool; #define true 1 #define false 0 const int DEBUG = 0; void getScreen2(const int, const int, const int, const int, const XID, unsigned char *); void write_png_for_image(XImage *image, XID xid, int width, int height, char *filename); typedef int (*handler)(Display *, XErrorEvent *); XID getWindows(Display *display, Window parent, Window window, XID xid, int depth); int main() { Display *display = XOpenDisplay("10.31.91.87:5"); Window root = DefaultRootWindow(display); uint nwindows; Window root_return, parent_return, *windows; Atom a = XInternAtom(display, "_NET_CLIENT_LIST", true); Atom actualType; int format; unsigned long numItems, bytesAfter; unsigned char *data = 0; int status = XGetWindowProperty(display, root, a, 0L, (~0L), false, AnyPropertyType, &actualType, &format, &numItems, &bytesAfter, &data); char* window_name_return; if (status >= Success && numItems) { long *array = (long*) data; for (long k = 0; k < numItems; k++) { Window window = (Window) array[k]; //not finding chrome window name printf("window found was %d \n", window); if (XFetchName(display, window, &window_name_return)) { printf("found window name for %d : %s \n", window, window_name_return); } //XMapWindow(display, parent); XMapWindow(display, window); XWindowAttributes attr; Status status = XGetWindowAttributes(display, window, &attr); if (status == 0) printf("Fail to get window attributes!\n"); unsigned char outdata[attr.width * attr.height * 3]; getScreen2(0, 0, attr.width, attr.height, window, outdata); } XFree(data); } return 0; } void getScreen2(const int xx, const int yy, const int W, const int H, const XID xid, /*out*/unsigned char * data) { Display *display = XOpenDisplay("10.31.91.87:5"); Window root = DefaultRootWindow(display); // turn on --sync to force error on correct method //https://www.x.org/releases/X11R7.6/doc/man/man3/XSynchronize.3.xhtml XSynchronize(display, True); int counter = 1; // select which xid to operate on, the winder or its parent //XID xwid = fparent; XID xwid = xid; // Requests the X server to direct the hierarchy starting at window to off-screen storage XCompositeRedirectWindow(display, xwid, CompositeRedirectAutomatic); XWindowAttributes attr; Status status = XGetWindowAttributes(display, xwid, &attr); int width = attr.width; int height = attr.height; int depth = attr.depth; Pixmap xc_pixmap = XCompositeNameWindowPixmap(display, xwid); if (!xc_pixmap) { printf("xc_pixmap not found\n"); } //XWriteBitmapFile(display, "test1.xpm", pixmap, W, H, -1, -1); XRenderPictFormat *format = XRenderFindVisualFormat(display, attr.visual); XRenderPictureAttributes pa; pa.subwindow_mode = IncludeInferiors; Picture picture = XRenderCreatePicture(display, xwid, format, CPSubwindowMode, &pa); char buffer[50]; int n; int file_counter = 1; n = sprintf(buffer, "/tmp/%d_test%d.xpm", xid, file_counter++); XWriteBitmapFile(display, buffer, xc_pixmap, W, H, -1, -1); n = sprintf(buffer, "/tmp/%d_test%d.xpm", xid, file_counter++); XWriteBitmapFile(display, buffer, xid, W, H, -1, -1); XImage *image = XGetImage(display, xid, 0, 0, W, H, AllPlanes, ZPixmap); if (!image) { printf("XGetImage failed\n"); } char filename[255]; int n2; n2 = sprintf(filename, "/tmp/png_out/%d_test%d.png", xid, file_counter++); printf("filename %s \n", filename); write_png_for_image(image, xid, W, H, filename); //XFree(image); XDestroyImage(image); XDestroyWindow(display, root); XCloseDisplay(display); } void write_png_for_image(XImage *image, XID xid, int width, int height, char *filename) { int code = 0; FILE *fp; png_structp png_ptr; png_infop png_info_ptr; png_bytep png_row; char buffer[50]; int n; n = sprintf(buffer, filename, xid); // Open file fp = fopen(buffer, "wb"); if (fp == NULL) { fprintf(stderr, "Could not open file for writing\n"); code = 1; } // Initialize write structure png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); if (png_ptr == NULL) { fprintf(stderr, "Could not allocate write struct\n"); code = 1; } // Initialize info structure png_info_ptr = png_create_info_struct(png_ptr); if (png_info_ptr == NULL) { fprintf(stderr, "Could not allocate info struct\n"); code = 1; } // Setup Exception handling if (setjmp(png_jmpbuf (png_ptr))) { fprintf(stderr, "Error during png creation\n"); code = 1; } png_init_io(png_ptr, fp); // Write header (8 bit colour depth) png_set_IHDR(png_ptr, png_info_ptr, width, height, 8, PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); // Set title char *title = "Screenshot"; if (title != NULL) { png_text title_text; title_text.compression = PNG_TEXT_COMPRESSION_NONE; title_text.key = "Title"; title_text.text = title; png_set_text(png_ptr, png_info_ptr, &title_text, 1); } png_write_info(png_ptr, png_info_ptr); // Allocate memory for one row (3 bytes per pixel - RGB) png_row = (png_bytep) malloc(3 * width * sizeof(png_byte)); unsigned long red_mask = image->red_mask; unsigned long green_mask = image->green_mask; unsigned long blue_mask = image->blue_mask; // Write image data //int xxx, yyy; for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { unsigned long pixel = XGetPixel(image, x, y); unsigned char blue = pixel & blue_mask; unsigned char green = (pixel & green_mask) >> 8; unsigned char red = (pixel & red_mask) >> 16; png_byte *ptr = &(png_row[x * 3]); ptr[0] = red; ptr[1] = green; ptr[2] = blue; } png_write_row(png_ptr, png_row); } // End write png_write_end(png_ptr, NULL); // Free fclose(fp); if (png_info_ptr != NULL) png_free_data(png_ptr, png_info_ptr, PNG_FREE_ALL, -1); if (png_ptr != NULL) png_destroy_write_struct(&png_ptr, (png_infopp) NULL); if (png_row != NULL) free(png_row); } |
窗口融合原理
FDE上要实现的窗口融合效果是Linux的每个X程序跟安卓的每个应用显示效果一致,使自己的程序独有窗口。当我们能够将每个X程序的图像从xserver的合成器中分离出来的时候,方案的前置条件就成立了。当然具体实现还要从以下几个方面着手。
1. 显示重定向
显示重定向是最容易理解的,就是把X程序对应用窗口的显示内容转到另一个单独的buffer中,FDE再把这个buffer拿去安卓中做显示,即是output的处理。
...
代码块 |
---|
static void lorieTimerCallback(int fd, unused int r, void *arg) { logh("lorieTimerCallback"); char dummy[8]; read(fd, dummy, 8); if (renderer_should_redraw() && RegionNotEmpty(DamageRegion(pvfb->damage))) { int redrawn = FALSE; ScreenPtr pScreen = (ScreenPtr) arg; loriePixmapUnlock(pScreen->GetScreenPixmap(pScreen)); redrawn = renderer_redraw(pvfb->env, pvfb->root.flip); if (loriePixmapLock(pScreen->GetScreenPixmap(pScreen)) && redrawn) DamageEmpty(pvfb->damage); } else if (pvfb->cursorMoved) renderer_redraw(pvfb->env, pvfb->root.flip); pvfb->cursorMoved = FALSE; } |
Action 2 : 重定向后buffer的生命周期管理或者说X程序生命周期的管理
可能要拿到以下确切数据,来构建安卓窗口:
connection建立过程,window创建过程,window的详细参数;
window分类,哪些需要新窗口显示,哪些在内部处理;
优化damage机制;
....
2. 事件重定向
Compositor扩展并没有处理事件重定向,意思就是说他不重定向输入事件。他会将OverlayWindow 的所有输入直接传到下层窗口,相当于什么也没做。
...
平移和缩放窗口,平移是在安卓窗口上的操作,对X程序的显示其实不产生影响,甚至可以不做处理,因为重定向之后,即使有堆叠也可以拿完全显示的X程序图像。缩放在安卓上几乎不需要太多改动,但是在xserver却要重绘,resize等操作,是一个需要把操作指令转换成xserver函数的工作。
...
Action 3:事件重定向的坐标转换,窗口ConfigreNotify
缩放时处理Randr;
坐标系转换;
快捷键捕获;
文本输入移植ibus?;
...
3. 安卓端实现
相较于Xserver的改动,安卓的实现简单,在VNC上的处理都可以直接移植,遗留有一个activity多栈未解决,也不是马上必须要解决的。
...
关于output,所有的X程序的图像buffer都有两份,虽然合成器合成后的那个图像并不会做显示,但是也占用了内存,需要评估最终效果。
Action 4 :需要在安卓端研究的工作:
复杂输入事件需要验证,比如组合事件是否能直接生效;
显示数据使用翻倍,内存有没有问题;
资源配置文件的完善(字体,字符,键盘布局文件等);
DecorcaptionView跟X程序操作按钮的替换;
...
...
窗口分离实验步骤
以上是原理方面的设想,当要做工程实现的时候,最好是在现有项目做一定的验证,而且只对关键部分做验证,显然显示重定向是决定方案是否可行的关键。但是之前要解决另一个问题,Android多SurfaceView渲染的问题。
Android多SurfaceView渲染
现在大部分安卓项目在使用opengl渲染的时候已经都学会自己配置EGL,而不是直接使用GlSurfaceView,好处是可以自己管理EGL环境,可以处理所有资源的申请和释放,不再跟view的生命周期绑定,不再使用原生的render,自己组织gl指令,满足各种特制化需求。
...
调试效果如下,这里的弹窗口是选项弹窗,未选特定窗口,不如看视频
...
Action 5:探究另一种可能的实现方式:Xserver多screen
每个X程序独占一个screen,所有X程序的input,output不产生关系,唯一需要处理焦点切换。
未来需要做的事 ACTION
改造termux/termux-x11;
重定向后buffer的生命周期管理或者说X程序生命周期的管理;
事件重定向的坐标转换,窗口ConfigreNotify;
复杂输入事件需要验证,比如组合事件是否能直接生效;
探究另一种可能的实现方式:Xserver多screen。
...