深度探索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 主题描述安装在正确的目录下。

目录
相关文章
|
4天前
|
人工智能 分布式计算 大数据
Linux操作系统:开源力量的崛起与影响###
一场技术革命的火种,如何燎原? 本文将带您深入探索Linux操作系统的诞生背景、核心特性及其对现代科技世界的深远影响。从1991年芬兰学生Linus Torvalds的一个小众项目,到如今成为支撑全球无数服务器、超级计算机及物联网设备的基石,Linux的发展既是一部技术创新史,也是开源文化胜利的见证。通过剖析其设计哲学、安全性、灵活性等关键优势,结合实例展示Linux在云计算、大数据处理等领域的广泛应用,本文旨在揭示Linux为何能在众多操作系统中脱颖而出,以及它如何塑造了我们今天的数字生活。 ###
|
2天前
|
Ubuntu 应用服务中间件 Linux
Linux下搭建Nginx环境的搭建
Linux下搭建Nginx环境的搭建
|
4天前
|
网络协议 Linux 调度
深入探索Linux操作系统的心脏:内核与系统调用####
本文旨在揭开Linux操作系统中最为核心的部分——内核与系统调用的神秘面纱,通过生动形象的语言和比喻,让读者仿佛踏上了一段奇妙的旅程,从宏观到微观,逐步深入了解这两个关键组件如何协同工作,支撑起整个操作系统的运行。不同于传统的技术解析,本文将以故事化的方式,带领读者领略Linux内核的精妙设计与系统调用的魅力所在,即便是对技术细节不甚了解的读者也能轻松享受这次知识之旅。 ####
|
1天前
|
缓存 算法 安全
深入理解Linux操作系统的心脏:内核与系统调用####
【10月更文挑战第20天】 本文将带你探索Linux操作系统的核心——其强大的内核和高效的系统调用机制。通过深入浅出的解释,我们将揭示这些技术是如何协同工作以支撑起整个系统的运行,同时也会触及一些常见的误解和背后的哲学思想。无论你是开发者、系统管理员还是普通用户,了解这些基础知识都将有助于你更好地利用Linux的强大功能。 ####
7 1
|
5天前
|
监控 Linux 云计算
Linux操作系统在云计算环境中的实践与优化###
【10月更文挑战第16天】 本文探讨了Linux操作系统在云计算环境中的应用实践,重点分析了其在稳定性、安全性和高效性方面的优势。通过具体案例,阐述了Linux如何支持虚拟化技术、实现资源高效分配以及与其他开源技术的无缝集成。文章还提供了针对Linux系统在云计算中的优化建议,包括内核参数调整、文件系统选择和性能监控工具的应用,旨在帮助读者更好地理解和应用Linux于云计算场景。 ###
13 3
|
2天前
|
安全 Linux 编译器
探索Linux内核的奥秘:从零构建操作系统####
本文旨在通过深入浅出的方式,带领读者踏上一段从零开始构建简化版Linux操作系统的旅程。我们将避开复杂的技术细节,以通俗易懂的语言,逐步揭开Linux内核的神秘面纱,探讨其工作原理、核心组件及如何通过实践加深理解。这既是一次对操作系统原理的深刻洞察,也是一场激发创新思维与实践能力的冒险。 ####
|
3天前
|
运维 安全 Linux
Linux中传输文件文件夹的10个scp命令
【10月更文挑战第18天】本文详细介绍了10种利用scp命令在Linux系统中进行文件传输的方法,涵盖基础文件传输、使用密钥认证、复制整个目录、从远程主机复制文件、同时传输多个文件和目录、保持文件权限、跨多台远程主机传输、指定端口及显示传输进度等场景,旨在帮助用户在不同情况下高效安全地完成文件传输任务。
31 5
|
3天前
|
Linux
Linux系统之expr命令的基本使用
【10月更文挑战第18天】Linux系统之expr命令的基本使用
25 4
|
1天前
|
监控 Linux Shell
|
4天前
|
Unix Linux
Linux | Rsync 命令:16 个实际示例(下)
Linux | Rsync 命令:16 个实际示例(下)
15 3
Linux | Rsync 命令:16 个实际示例(下)