深度探索Linux操作系统 —— 构建桌面环境3

简介: 深度探索Linux操作系统 —— 构建桌面环境

深度探索Linux操作系统 —— 构建桌面环境2:https://developer.aliyun.com/article/1598093

6、构建窗口装饰

   仅给窗口 “落户” 还是不够的,接下来我们还需要为窗口构建装饰。除了起到美化作用外,这些装饰还是用户和应用的窗口之间的桥梁。用户可以通过标题栏移动窗口位置,可以通过边框改变窗口尺寸,可以点击最大化按钮将窗口最大化,可点击最小化按钮将窗口最小化,可以点击关闭按钮关闭窗口。


   在创建了窗口对象后,函数 wm_new_client 中调用窗口对象中函数指针 reparent 指向的函数来构建窗口装饰。对于标准窗口来说,构建窗口装饰的函数是 normal_client_reparent 。

// winman/src/normal_client.c:
static void normal_client_reparent(Client *c) {
  XSetWindowAttributes attr;
  WinMan *wm = c->wm;
  int frame_x, frame_y;
  XColor tc, sc;
  
  if (normal_client_calc_geometry(c))
    XMoveResizeWindow(wm->dpy, c->window, c->x, c->y,
        c->width, c->height);
        
  XAllocNamedColor(wm->dpy, DefaultColormap(wm->dpy,
        wm->screen), LIGHTGRAY, &sc, &tc);
        
  attr.background_pixel = sc.pixel;
  attr.override_redirect = True;
  attr.event_mask = SubstructureRedirectMask
          | SubstructureNotifyMask
          | ExposureMask
          | ButtonPressMask
          | ButtonReleaseMask
          | Button1MotionMask;
  c->frame = XCreateWindow(wm->dpy, wm->root,
      c->x - BORDER_WIDTH,
      c->y - BORDER_WIDTH - TITLEBAR_HEIGHT,
      c->width + BORDER_WIDTH * 2,
      c->height + TITLEBAR_HEIGHT + BORDER_WIDTH * 2, 0,
      CopyFromParent, CopyFromParent, CopyFromParent,
      CWOverrideRedirect | CWBackPixel | CWEventMask,
      &attr);
  
  XDefineCursor(wm->dpy, c->frame,
      XCreateFontCursor(wm->dpy, XC_arrow));
      
  attr.event_mask = ExposureMask;
  c->titlebar = XCreateWindow(…);
  ...
  c->rsz_ul_angle = XCreateWindow(wm->dpy, c->frame,
      0, 0, RSZ_ANGLE_SIZE, RSZ_ANGLE_SIZE, 0,
      CopyFromParent, InputOnly, CopyFromParent,
      CWOverrideRedirect, &attr);
      
  XDefineCursor(wm->dpy, c->rsz_ul_angle,
      XCreateFontCursor(wm->dpy, XC_ul_angle));
  XLowerWindow(wm->dpy, c->rsz_ul_angle);
  ...
  XAddToSaveSet(wm->dpy, c->window);
  XReparentWindow(wm->dpy, c->window, c->frame, BORDER_WIDTH,
      TITLEBAR_HEIGHT + BORDER_WIDTH);
}

7、绘制装饰窗口

   在 7.1.6 节中,winman 创建了各个装饰窗口,但是并没有为各装饰窗口绘制内容。事实上,即使 winman 想去绘制,也是有心无力。基于 X 的原理,X 服务器并不保存窗口的内容,在窗口可见时,X 服务器会向应用报告 Expose 事件,应用收到这个事件后,开始绘制。否则即使应用在创建窗口时自说自话地进行了绘制,也会被丢掉。


   因此,在函数 wm_new_client 中,在构建了窗口装饰后,调用了窗口对象中函数指针 show 指向的函数,请求 X 服务器进行显示。对于标准窗口来说,请求 X 服务器显示窗口是 normal_client_show 。


8、配置窗口

   通常,我们在编写具有图形界面的应用程序时,在显示图形界面之前,一定会设置窗口的位置、尺寸或者边框宽度等。虽然读者可能反驳说,我们有时并没有设置这些啊?实际上,那是因为如 GTK、QT 等图形库已经帮我们做了。另外一种情况是在应用运行的某个中间时刻,应用也可能会改变窗口的这些信息。


   X 将这些信息统称为窗口配置,包括窗口的位置、宽度和高度,边框的宽度以及在栈中的位置。


   在上述两种情况下,应用都将产生配置请求,X 服务器也都会将它们重定向给窗口管理器。那么窗口管理器如何区分这两种情况呢?winman 是这样处理的,当收到 X 服务器重定向来的配置请求时, winman 调用函数 wm_find_client_by_window 遍历窗口栈,如果窗口栈中没有一个窗口对象与发送请求的窗口匹配,就说明这个窗口尚未被管理,否则说明这个窗口已经被管理了。


   对于尚未纳入管理的应用的窗口,winman 当然不能贸然管理,谁知道未来它是否需要管理呢。所以直接请求 X 服务器满足其需要。winman 从事件 XConfigureRequestEvent 中提取信息,不加任何修改,完全照搬原来的配置请求,使用 Xlib 的函数 XConfigureWindow 直接代替应用向 X 服务器发出配置请求。


   对于已被管理的窗口,winman 调用具体窗口对象中处理配置的函数进行具体的配置。


9、移动窗口

   对于一个典型的桌面应用来说,用户可以通过拖动窗口的标题栏来移动窗口。这里所谓的 “拖动” 的具体动作是:在标题栏上按下鼠标左键并保持,然后移动鼠标,直到释放鼠标。


   因此,整个移动窗口的过程可以划分为三个阶段:


   1)用户在标题栏上按下鼠标左键并保持;

   2)用户移动鼠标;

   3)用户释放鼠标,移动结束。


   为此,结构体 Client 中设计了布尔变量 moving ,当用户在标题栏内按下鼠标左键时,moving 将被置为 True 。当鼠标移动事件发生时,如果变量 moving 为 True,那么我们就可以断定用户是在移动窗口。一旦用户释放了鼠标,winman 将 moving 更改为 False 。


10、改变窗口大小

   改变窗口大小与移动窗口的操作逻辑上基本相同,这里只简要讨论实现的逻辑,就不再列出具体代码了,请读者自行参考随书光盘中附带的源代码。


   在用户按下鼠标事件时,将鼠标指针所在的标识移动区域的窗口,也就是结构体 Client 中以 rsz_ 开头的窗口,记录到窗口对象的成员 resizing_area 中。


   然后,当收到鼠标移动事件时,如果窗口对象的成员 resizing_area 非 0,那就说明用户正在试图改变窗口大小。根据 resizing_area 与 8 个标识移动区域的窗口对比,推断出用户正在如何更改窗口的大小,然后计算出窗口改变后的几何信息,请求 X 服务器改变窗口大小。


   当鼠标释放时,将 resizing_area 清 0 。


11、切换窗口

   我们以图 7-12 所示的场景为例来讨论窗口之间的切换。应用 A 和应用 B 分别为两个 X 应用,图中使用虚线标识的是应用创建的窗口,实线标出的是窗口管理器创建的窗口。A1 是应用 A 的标准类型的顶层窗口,A2 是对话框类型的顶层窗口,且 A2 是窗口 A1 的临时窗口。B1 是应用 B 的标准类型的顶层窗口,B2 是对话框类型的顶层窗口,且 B2 是窗口 B1 的临时窗口。初始状态 X 时,应用 A 是当前活动的应用;在状态为 Y 时,应用 B 被切换为当前的活动应用。


496b2cc157d29c216889d20468700104.png


12、最大化/最小化/关闭窗口

   本节我们讨论最大化、最小化及关闭窗口的相关知识。


1. 最小化窗口

   最小化窗口本质上就是取消窗口的显示,Xlib 为此提供了相应的函数 XUnmapWindow 。当取消某个窗口的显示时,同时也需要取消其临时窗口的显示。


2. 最大化/恢复窗口

   所谓的最大化/恢复窗口,本质上就是使用 Xlib 的类似如 XMoveResizeWindow 的函数调整窗口位置和大小,winman 中代码中对应的实现是 maximize_window 函数。


   唯一需要指出的就是, winman 自定义了属性 _CUSTOM_WM_RESTORE_GEOMETRY,在最大化之前将窗口的几何信息,包括位置、高度和宽度,都记录到窗口的属性中。为什么要在窗口的属性中记录,不是都已经记录到窗口对象中了吗?试想一下,如果不在窗口的属性中记录,而只是记录在窗口管理器中,一旦窗口管理器异常退出,那么一切状态信息将随着窗口管理器灰飞烟灭。为了窗口管理器再次启动时能获得这些信息,将这些信息保存在窗口中是一个合理的办法。


   类似地,在最大化/恢复窗口时,winman 也更新了窗口的另外两个属性 _NET_WM_STATE_MAXIMIZED_VERT 和 _NET_WM_STATE_MAXIMIZED_HORZ 。


3. 关闭窗口

   ICCCM 规范规定,当关闭窗口时,窗口管理器应该发送消息 WM_DELETE_WINDOW 给应用,而不是越俎代庖地请求 X 服务器去销毁应用的窗口。因为应用收到消息 WM_DELETE_WINDOW 后,可以做一些善后处理,然后在请求 X 服务器关闭窗口。


   当然,有些应用程序不是很守规矩,尤其是早期使用 Xlib 编写的程序,它们不处理消息 WM_DELETE_WINDOW 。对于这类窗口,也只能采用简单粗暴的方法了,直接使用 Xlib 提供的函数 XKillClient 断开应用程序到X服务器的连接,这也就意味着整个 X 应用彻底退出执行。


   那么窗口管理器如何得知应用是否处理了事件 WM_DELETE_WINDOW ? ICCCM 规范规定,如果窗口自己负责销毁,其应该在窗口的属性 WM_PROTOCOLS 中设置属性 WM_DELETE_WINDOW 。属性 WM_PROTOCOLS 的值是个 Atom 数组,其中包括多个属性。


13、管理已存在的窗口

   在窗口管理器启动之前,可能有一些应用已经在运行。因此,在窗口管理器启动时,需要管理这些已存在的窗口。这就是 winman 的初始化时调用函数 init_clients 的目的。


二、任务条和桌面

   从最初出现在桌面环境中发展到现在,任务条的风格也在不断地发生改变,但依然是桌面环境的重要组件之一,只不过表现形式并不一定是千篇一律。


   典型的任务条从左至右包括 “开始按钮” 、“快速启动栏” 、“任务项” 以及 “通知区域”。 用户通过 “开始按钮” 可以启动应用程序;“快速启动栏” 中放置用户常用的一些程序;每个启动的任务都有一个 “任务项” ;“通知区域” 主要用来显示一些系统状态,比如显示当前的输入法、网络状态等。


   除了任务条外,一般的桌面环境都有一个背景,并且在这个背景上面可以显示一些快捷方式,可以显示一些很有个性的小插件。


   本章中,我们实现了一个简单的任务条和一个桌面。不同的桌面环境,实现这些组件的逻辑不尽相同,有的是放在一个完整的程序中,有的是每个组件是一个单独的程序,我们采用后者。我们通过这两个程序向读者展示使用图形库(GTK)编程。相比于 Xlib,GTK 的编程理解起来要容易得多,而且 GTK 的官方文档写得也非常详尽,所以我们就不浪费篇幅讨论有关 GTK 的编程了,这里仅讨论其中与窗口管理器相关的部分。


1、标识任务条的身份

   虽然任务条也是一个普通 X 应用,但是作为桌面环境中重要的一个组件,还是有一些特殊的地方。比如,在我们构建的桌面环境中,窗口管理器将其停靠在屏幕的最下方。但是任务条如何向 winman 亮明自己的任务身份呢?读者一定已经猜到了:属性。任务条自定义了属性 _CUSTOM_WM_WINDOW_TYPE_TASKBAR ,在启动时,其将窗口的属性 _NET_WM_WINDOW_TYPE 设置为属性 _CUSTOM_WM_WINDOW_TYPE_TASKBAR 。


2、更新任务条上的任务项

   前面我们看到,在 winman 中,每当为一个窗口 “落户” 时,winman 都将更新根窗口的属性 _NET_CLIENT_LIST_STACKING 。因此,任务条利用的就是这个机制,监测根窗口属性的变化,从而跟踪系统中任务的变化,相关代码如下:


962fbaf4ad96688198027dabf0073e05.png


   在任务条初始化时,其将选择根窗口事件掩码 PropertyChangeMask ,并设置根窗口的属性变化事件的回调函数为 root_window_event_filter 。如此,一旦根窗口的属性发生变化时,任务条都将洞悉。


   每当根窗口的属性 _NET_CLIENT_LIST_STACKING 发生变化时,函数 taskbar_setup_items 就读取根窗口的该属性的值,获取目前系统中全部的窗口列表。然后遍历这个列表,更新任务栏。为了简单,该函数做了很多简化,比如

只要窗口类型是 _NET_WM_WINDOW_TYPE_NORMAL ,并且也没有判断窗口是否是其他窗口的临时窗口,任务条就为其在任务条上创建一个任务项。


   作为桌面环境的核心组件之一,在桌面环境启动时,任务条是首先启动的核心组件之一。理论上,这个时候还没有应用启动,但是不排除系统运行过程中,任务条重新启动,谁也不能保证程序完全没有 bug 。因此,无论如何,任务条还是有必要在启动时获取系统中正在运行的任务,并为它们在任务条上建立相应的任务项。


3、激活任务

   任务条的另外一个主要任务就是将最小化的,或者将非活动的窗口激活为当前活动窗口。


   EWMH 规范规定,如果一个 X 应用希望激活另外一个窗口,可以通过向根窗口发送消息 _NET_ACTIVE_WINDOW 来实现。因此,在我们的任务条中,当用户点击任务按钮时,在回调函数中将向根窗口发送 ClientMessage 事件,其中的消息类型为 _NET_ACTIVE_WINDOW 。


4、高亮显示当前活动任务

   当某个任务成为当前活动任务时,任务条需要将对应的任务项特殊标识一下。那么任务条如何知道当前任务已经发生变化了呢?前面我们看到,在 winman 中,每当为将一个窗口设置为当前活动窗口时,winman 都将更新根窗口的属性 _NET_ACTIVE_WINDOW 。看到这里,读者一定明白了,任务条的处理过程与 7.2.2 节基本完全相同。


5、显示桌面

   当用户按下快速启动栏上的显示桌面按钮时,将把桌面显示到所有窗口的最前面。本章讨论到这里,我想读者应该已经大致可以猜出这个故事的脚本了:


   1)任务条向根窗口发送类型为 ClientMessage 的事件,EWMH 规范规定这个事件中的消息类型为 _NET_SHOWING_DESKTOP 。


   2)winman 请求 X 服务器将将桌面这个组件显示到窗口栈的最上面。winman 中的实现与切换窗口基本完全相同。


6、桌面

   相比于任务条,这个示例的桌面程序要简单很多。而且,经过了前面任务条的讨论,我想读者应该不需要笔者再过多的啰唆了。同普通应用对比,其比较特殊的地方之一就是,要向窗口管理器亮明自己的身份,代码如下所示:

// desktop/src/main.c:

int main(int argc, char *argv[]) {
  ...
  gtk_window_set_type_hint(GTK_WINDOW(win), GDK_WINDOW_TYPE_HINT_DESKTOP);
  ...
}

   桌面程序使用标准的 EWMH 规范规定的属性 _NET_WM_WINDOW_TYPE_DESKTOP 标识该程序是一个桌面程序。GTK 中的函数 gtk_window_set_type_hint 就是对 Xlib 的函数 XChangeProperty 的更高层的封装,我们直接使用即可。


   winman 将为桌面程序创建桌面窗口对象,并将其整个铺满在桌面背景上。同样,桌面窗口对象也不需要装饰,因此其函数 desktop_client_reparent 也是个空函数。其他细节,请读者参考随书光盘中附带的源代码。


   至此,一个基本的桌面环境就已经搭建完毕了,读者将它们安装到 vita 系统,然后使用如下命令即可启动完整的桌面环境:


813996b85956978d19fe2f013c4e2e03.png


   注意,桌面程序上的快捷方式 “Hello World” 的回调函数将到目录 /usr/bin 下寻找程序 hello_gtk ,所以请将这个程序复制到目录 /usr/bin 下。另外,也请确保程序 taskbar、desktop 使用的 css 主题描述安装在正确的目录下。

目录
相关文章
|
11天前
|
算法 Linux 调度
深入理解Linux操作系统的进程管理
本文旨在探讨Linux操作系统中的进程管理机制,包括进程的创建、执行、调度和终止等环节。通过对Linux内核中相关模块的分析,揭示其高效的进程管理策略,为开发者提供优化程序性能和资源利用率的参考。
34 1
|
13天前
|
安全 Linux 数据安全/隐私保护
深入Linux操作系统:文件系统和权限管理
在数字世界的海洋中,操作系统是连接用户与硬件的桥梁,而Linux作为其中的佼佼者,其文件系统和权限管理则是这座桥梁上不可或缺的结构。本文将带你探索Linux的文件系统结构,理解文件权限的重要性,并通过实际案例揭示如何有效地管理和控制这些权限。我们将一起航行在Linux的命令行海洋中,解锁文件系统的奥秘,并学习如何保护你的数据免受不必要的访问。
|
13天前
|
搜索推荐 Linux
深入理解Linux操作系统的启动过程
本文旨在揭示Linux操作系统从开机到完全启动的神秘面纱,通过逐步解析BIOS、引导加载程序、内核初始化等关键步骤,帮助读者建立对Linux启动流程的清晰认识。我们将探讨如何自定义和优化这一过程,以实现更高效、更稳定的系统运行。
|
11天前
|
存储 缓存 网络协议
Linux操作系统的内核优化与性能调优####
本文深入探讨了Linux操作系统内核的优化策略与性能调优方法,旨在为系统管理员和高级用户提供一套实用的指南。通过分析内核参数调整、文件系统选择、内存管理及网络配置等关键方面,本文揭示了如何有效提升Linux系统的稳定性和运行效率。不同于常规摘要仅概述内容的做法,本摘要直接指出文章的核心价值——提供具体可行的优化措施,助力读者实现系统性能的飞跃。 ####
|
12天前
|
缓存 监控 网络协议
Linux操作系统的内核优化与实践####
本文旨在探讨Linux操作系统内核的优化策略与实际应用案例,深入分析内核参数调优、编译选项配置及实时性能监控的方法。通过具体实例讲解如何根据不同应用场景调整内核设置,以提升系统性能和稳定性,为系统管理员和技术爱好者提供实用的优化指南。 ####
|
1月前
|
安全 Linux 数据安全/隐私保护
Vanilla OS:下一代安全 Linux 发行版
【10月更文挑战第30天】
53 0
Vanilla OS:下一代安全 Linux 发行版
|
26天前
|
NoSQL Linux PHP
如何在不同操作系统上安装 Redis 服务器,包括 Linux 和 Windows 的具体步骤
本文介绍了如何在不同操作系统上安装 Redis 服务器,包括 Linux 和 Windows 的具体步骤。接着,对比了两种常用的 PHP Redis 客户端扩展:PhpRedis 和 Predis,详细说明了它们的安装方法及优缺点。最后,提供了使用 PhpRedis 和 Predis 在 PHP 中连接 Redis 服务器及进行字符串、列表、集合和哈希等数据类型的基本操作示例。
50 4
|
1月前
|
人工智能 安全 Linux
|
2月前
|
Unix 物联网 大数据
操作系统的演化与比较:从Unix到Linux
本文将探讨操作系统的历史发展,重点关注Unix和Linux两个主要的操作系统分支。通过分析它们的起源、设计哲学、技术特点以及在现代计算中的影响,我们可以更好地理解操作系统在计算机科学中的核心地位及其未来发展趋势。
|
4月前
|
编解码 安全 Linux
基于arm64架构国产操作系统|Linux下的RTMP|RTSP低延时直播播放器开发探究
这段内容讲述了国产操作系统背景下,大牛直播SDK针对国产操作系统与Linux平台发布的RTMP/RTSP直播播放SDK。此SDK支持arm64架构,基于X协议输出视频,采用PulseAudio和Alsa Lib处理音频,具备实时静音、快照、缓冲时间设定等功能,并支持H.265编码格式。此外,提供了示例代码展示如何实现多实例播放器的创建与管理,包括窗口布局调整、事件监听、视频分辨率变化和实时快照回调等关键功能。这一技术实现有助于提高直播服务的稳定性和响应速度,适应国产操作系统在各行业中的应用需求。
134 3