3、Screen API基础:Screen库的初始化与销毁、连接QNX图形服务器、获取屏幕信息
好,我们正式开始接触Screen API。说实话,很多刚接触QNX图形开发的朋友,第一反应就是——这玩意儿怎么跟Linux那套完全不一样?嗯,确实不一样。Screen是QNX原生的图形框架,它不依赖X11,也不走Wayland。你想想看,在嵌入式场景下,我们需要的是轻量、实时、可控。Screen就是为这个而生的。
我个人习惯,学一个新API,先搞清楚三件事:怎么连上去、怎么拿信息、怎么断开。今天我们就围绕这三步展开。
3.1 Screen库的初始化
初始化Screen库,说白了就是告诉系统:“嘿,我要用图形功能了。” 这一步很简单,但也很关键。你如果忘了初始化,后面所有调用都会返回错误。
核心函数就一个:screen_init()。它的原型长这样:
#include <screen/screen.h>
int screen_init(screen_context_t *ctx, const char *name);
参数说明:
ctx:输出参数,初始化成功后,你会得到一个上下文句柄。这个句柄后面所有操作都要用到。name:应用名称。可以传NULL,但我建议你传一个有意义的名字。为什么?因为QNX的图形服务器会用这个名字来标识你的应用,调试时一眼就能认出来。
返回值:成功返回0,失败返回-1。嗯,这里要注意,很多新手会忽略检查返回值。我曾经在项目里见过一个同事,初始化失败后直接往下走,结果后面所有screen调用都返回奇怪错误,排查了半天才发现是初始化没成功。
来看一个完整的初始化示例:
screen_context_t screen_ctx;
int rc;
rc = screen_init(&screen_ctx, "my_app");
if (rc != 0) {
fprintf(stderr, "screen_init failed: %s\n", strerror(errno));
return -1;
}
printf("Screen initialized successfully.\n");
你看,就这么几行。但这里有个细节——screen_context_t 其实是一个不透明指针。你不需要关心它内部是什么结构,只需要知道它是你连接图形服务器的“钥匙”就行。
3.2 连接QNX图形服务器
初始化之后,下一步就是连接图形服务器。其实,screen_init() 内部已经帮你做了连接工作。它会在初始化时自动与QNX的图形服务器(Screen Server)建立通信通道。
但这里有个概念我想强调一下:Screen的架构是典型的客户端-服务器模型。你的应用是客户端,Screen Server是服务端。所有绘图操作、窗口管理、事件处理,都是通过这个通道来完成的。
我记得第一次接触这个模型时,总觉得有点绕。后来想通了——你想想看,如果每个应用都直接操作显存,那不乱套了?所以必须有个中央调度者,也就是Screen Server。
连接成功后,你可以通过 screen_get_context_property_iv() 来获取一些全局信息,比如当前连接的服务器版本:
int version;
screen_get_context_property_iv(screen_ctx, SCREEN_PROPERTY_VERSION, &version);
printf("Screen server version: %d\n", version);
这个版本号很有用。我在项目中遇到过,不同版本的Screen API在行为上有细微差异。比如早期版本不支持某些窗口特效,如果你没检查版本就直接用,程序可能会崩溃。
3.3 获取屏幕信息
连接上服务器后,我们最常做的事就是获取屏幕信息。比如分辨率、像素格式、刷新率等等。这些信息都存储在 screen_display_t 对象中。
获取屏幕信息的流程分三步:
- 获取当前可用的显示器列表
- 选择你要操作的显示器(通常是第一个)
- 读取该显示器的属性
来看代码:
int display_count;
screen_display_t *displays;
// 第一步:获取显示器数量
screen_get_context_property_iv(screen_ctx, SCREEN_PROPERTY_DISPLAY_COUNT, &display_count);
printf("Number of displays: %d\n", display_count);
// 第二步:分配空间并获取显示器列表
displays = calloc(display_count, sizeof(screen_display_t));
screen_get_context_property_pv(screen_ctx, SCREEN_PROPERTY_DISPLAYS, (void**)displays);
// 第三步:读取第一个显示器的信息
int width, height, format;
screen_get_display_property_iv(displays[0], SCREEN_PROPERTY_WIDTH, &width);
screen_get_display_property_iv(displays[0], SCREEN_PROPERTY_HEIGHT, &height);
screen_get_display_property_iv(displays[0], SCREEN_PROPERTY_FORMAT, &format);
printf("Display 0: %dx%d, format=%d\n", width, height, format);
这里有几个关键点:
SCREEN_PROPERTY_DISPLAY_COUNT返回的是物理显示器的数量。在嵌入式设备上,通常只有一个。但有些设备(比如车载仪表盘)可能有多个屏幕。SCREEN_PROPERTY_DISPLAYS返回的是screen_display_t指针数组。注意,这里用的是_pv后缀,因为返回的是指针类型。- 像素格式
SCREEN_PROPERTY_FORMAT返回的是一个枚举值,比如SCREEN_FORMAT_RGBX8888或SCREEN_FORMAT_RGBA8888。这个信息在后面创建窗口和缓冲区时非常重要。
除了宽高和格式,你还可以获取很多其他属性。我整理了一个常用属性表:
| 属性常量 | 类型 | 说明 |
|---|---|---|
| SCREEN_PROPERTY_WIDTH | int | 屏幕宽度(像素) |
| SCREEN_PROPERTY_HEIGHT | int | 屏幕高度(像素) |
| SCREEN_PROPERTY_FORMAT | int | 像素格式枚举值 |
| SCREEN_PROPERTY_PHYSICAL_WIDTH | int | 物理宽度(毫米) |
| SCREEN_PROPERTY_PHYSICAL_HEIGHT | int | 物理高度(毫米) |
| SCREEN_PROPERTY_REFRESH_RATE | int | 刷新率(毫赫兹,除以1000得到Hz) |
| SCREEN_PROPERTY_ROTATION | int | 当前旋转角度(0/90/180/270) |
嗯,这里要注意 SCREEN_PROPERTY_REFRESH_RATE 的单位是毫赫兹。也就是说,如果返回60000,实际刷新率是60Hz。我第一次用的时候没注意单位,直接当Hz用了,结果计算出来的帧间隔完全不对。
3.4 销毁与清理
有初始化就得有销毁。Screen库的清理工作很简单,就一个函数:
screen_destroy(screen_ctx);
调用 screen_destroy() 后,Screen库会释放所有内部资源,断开与图形服务器的连接。之后你就不能再使用任何Screen API了,除非重新调用 screen_init()。
这里有个常见的坑:如果你在初始化后创建了窗口、缓冲区等资源,screen_destroy() 并不会自动销毁它们。你需要先手动销毁这些子资源,再调用 screen_destroy()。否则,可能会造成资源泄漏。
一个完整的生命周期示例:
screen_context_t screen_ctx;
// 初始化
if (screen_init(&screen_ctx, "my_app") != 0) {
// 处理错误
}
// ... 创建窗口、缓冲区,进行绘图操作 ...
// 清理:先销毁子资源
// screen_destroy_window(...);
// screen_destroy_buffer(...);
// 最后销毁上下文
screen_destroy(screen_ctx);
好了,这一章的内容就到这里。我们走完了Screen API的“连接-获取信息-断开”全流程。下一章,我们会深入窗口的创建与管理。到时候你会看到,今天学的这些基础操作,是后面所有高级功能的地基。