最近工作中遇到一个小问题,就是需要获取屏幕工作区域以及任务栏区域,之前采用的其他三方库封装的接口,发现在开发测试过程中会存在工作区获取异常的问题,于是放弃第三方接口,采用原生X11/Xrandr实现。
因为使用XGetWindowProperty获取"_NET_WORKAREA"这个属性是获取的整个虚拟屏幕(多个屏幕组合)的区域,如果存在多扩展屏的情况则会有问题,所以实现思路如下:
1. 先使用XRRGetScreenResources获取所有屏幕信息
2. 使用XGetWindowProperty获取虚拟工作区
3. 计算主屏任务栏区域,并将主屏任务栏高度适用到其他副屏
具体代码实现如下:
struct Screen_t {
std::string name; // 屏幕名称
int index = -1; // 屏幕索引(一般0为主屏)
int x, y = 0; // 屏幕位置
int width, height = 0; // 屏幕分辨率
int work_x, work_y = 0; // 工作区位置(相对于屏幕)
int work_width, work_height = 0; // 工作区大小
int taskbar_x, taskbar_y = 0; // 任务栏位置(相对于屏幕)
int taskbar_width, taskbar_height = 0; // 任务栏大小
};
std::vector<Screen_t> get_screen_infos(Display* display, Window root) {
std::vector<Screen_t> screens;
// 获取所有屏幕资源
XRRScreenResources* res = XRRGetScreenResources(display, root);
if (!res) {
return screens;
}
// 获取当前活动输出
XRROutputInfo* output_info;
XRRCrtcInfo* crtc_info;
// 获取所有屏幕的基本信息
for (int i = 0; i < res->noutput; i++) {
output_info = XRRGetOutputInfo(display, res, res->outputs[i]);
if (output_info->connection == RR_Connected) {
crtc_info = XRRGetCrtcInfo(display, res, output_info->crtc);
// 屏幕信息
Screen_t screen;
screen.name = output_info->name;
screen.index = i;
screen.x = crtc_info->x;
screen.y = crtc_info->y;
screen.width = crtc_info->width;
screen.height = crtc_info->height;
// 初始化屏幕工作区信息
screen.work_x = 0;
screen.work_y = 0;
screen.work_width = crtc_info->width;
screen.work_height = crtc_info->height;
// 初始化屏幕任务栏信息
screen.taskbar_x = 0;
screen.taskbar_y = 0;
screen.taskbar_width = 0;
screen.taskbar_height = 0;
screens.push_back(screen);
XRRFreeCrtcInfo(crtc_info);
}
XRRFreeOutputInfo(output_info);
}
// 获取当前显示工作区信息
Atom actual_type;
int actual_format;
unsigned long nitems, bytes_after;
unsigned char* data = NULL;
Atom property = XInternAtom(display, "_NET_WORKAREA", False);
if (XGetWindowProperty(display, root, property, 0, (~0L),
False, AnyPropertyType, &actual_type,
&actual_format, &nitems, &bytes_after,
&data) == Success && data) {
if (nitems >= 4) {
long* workarea = (long*)data;
// 计算主屏任务栏高度
int primary_taskbar_height = 0;
if (!screens.empty()) {
Screen_t& primary = screens[0];
long wa_bottom = workarea[1] + workarea[3];
primary_taskbar_height = primary.height - (wa_bottom - primary.y);
// 主屏任务栏信息(目前只考虑了任务栏在底部的情况)
primary.taskbar_x = 0;
primary.taskbar_y = primary.height - primary_taskbar_height;
primary.taskbar_width = primary.width;
primary.taskbar_height = primary_taskbar_height;
}
// 处理剩下屏幕
for (auto& screen : screens) {
int screen_right = screen.x + screen.width;
int screen_bottom = screen.y + screen.height;
int wa_right = workarea[0] + workarea[2];
int wa_bottom = workarea[1] + workarea[3];
int intersect_x1 = std::max(screen.x, (int)workarea[0]);
int intersect_y1 = std::max(screen.y, (int)workarea[1]);
int intersect_x2 = std::min(screen_right, wa_right);
int intersect_y2 = std::min(screen_bottom, wa_bottom);
if (intersect_x1 < intersect_x2 && intersect_y1 < intersect_y2) {
// 计算工作区
screen.work_x = intersect_x1 - screen.x;
screen.work_y = intersect_y1 - screen.y;
screen.work_width = intersect_x2 - intersect_x1;
screen.work_height = intersect_y2 - intersect_y1;
// 默认使用主屏任务栏高度
if (screen.index != 0) {
screen.taskbar_x = 0;
screen.taskbar_y = screen.height - primary_taskbar_height;
screen.taskbar_width = screen.width;
screen.taskbar_height = primary_taskbar_height;
screen.work_height = screen.height - primary_taskbar_height - screen.work_y;
}
}
}
}
XFree(data);
}
XRRFreeScreenResources(res);
return screens;
}