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

目录
相关文章
|
22天前
|
机器学习/深度学习 Dart 前端开发
移动应用与系统:构建现代数字生态的基石在当今这个高度数字化的社会中,移动应用与操作系统已成为我们日常生活不可或缺的一部分。它们不仅改变了我们的沟通方式,还重塑了我们的工作、学习和娱乐模式。本文将深入探讨移动应用开发的基础、移动操作系统的功能以及这两者如何共同塑造了我们的数字世界。
随着智能手机和平板电脑的普及,移动应用与系统的重要性日益凸显。它们不仅为用户提供了便捷的服务和丰富的功能,还为开发者提供了广阔的创新平台。本文将介绍移动应用开发的基本概念、技术栈以及最佳实践,并探讨主流移动操作系统的特点和发展趋势。通过分析移动应用与系统的相互作用,我们可以更好地理解它们在现代社会中的重要地位。
|
1天前
|
安全 Linux 网络安全
Linux环境中安装和使用Paramiko
Linux环境中安装和使用Paramiko
24 12
|
3天前
|
Linux 数据安全/隐私保护
探索Linux操作系统下的权限管理
【8月更文挑战第66天】在数字世界中,操作系统的权限管理就如同现实世界中的钥匙和锁,保护着我们的数据安全。本文将带你深入理解Linux系统中的权限设置,通过实际代码示例,让你掌握文件和目录权限的分配与管理技巧。准备好了吗?让我们开始这场关于权限管理的探险之旅吧!
46 14
|
8天前
|
人工智能 安全 数据安全/隐私保护
移动应用与系统:构建现代数字生态的双引擎在数字化浪潮席卷全球的今天,移动应用与系统作为连接用户与数字世界的重要桥梁,正发挥着越来越重要的作用。它们不仅改变了人们的生活方式,也推动了各行各业的创新与发展。本文将深入探讨移动应用开发的最新趋势、移动操作系统的演进历程以及两者如何共同塑造我们的未来。
随着智能手机的普及和移动互联网的快速发展,移动应用已成为人们日常生活中不可或缺的一部分。从社交媒体到移动支付,从在线教育到远程办公,移动应用无处不在,极大地丰富了我们的生活体验。与此同时,移动操作系统也在不断进化,为应用提供了更加稳定、高效的运行环境。本文旨在分析当前移动应用开发的热点技术、探讨移动操作系统的未来发展方向,并展望这两者如何相互促进,共同推动数字经济的繁荣。
|
8天前
|
安全 Linux 数据安全/隐私保护
探索Linux操作系统的文件权限管理
【9月更文挑战第29天】在数字世界中,文件权限管理如同保护我们隐私的锁。本文将带你了解如何在Linux系统中设置和管理文件权限,确保你的数据安全。我们将一起学习如何通过命令行工具来控制文件访问,就像学习一门新语言一样有趣。准备好了吗?让我们一起开启这场技术之旅!
|
11天前
|
消息中间件 分布式计算 Java
Linux环境下 java程序提交spark任务到Yarn报错
Linux环境下 java程序提交spark任务到Yarn报错
21 5
|
11天前
|
Java Linux Python
Linux环境下 代码java调用python出错
Linux环境下 代码java调用python出错
27 3
|
11天前
|
Oracle Java 关系型数据库
Linux下JDK环境的配置及 bash: /usr/local/java/bin/java: cannot execute binary file: exec format error问题的解决
如果遇到"exec format error"问题,文章建议先检查Linux操作系统是32位还是64位,并确保安装了与系统匹配的JDK版本。如果系统是64位的,但出现了错误,可能是因为下载了错误的JDK版本。文章提供了一个链接,指向Oracle官网上的JDK 17 Linux版本下载页面,并附有截图说明。
Linux下JDK环境的配置及 bash: /usr/local/java/bin/java: cannot execute binary file: exec format error问题的解决
|
13天前
|
Linux 编译器 开发工具
快速在linux上配置python3.x的环境以及可能报错的解决方案(python其它版本可同样方式安装)
这篇文章介绍了在Linux系统上配置Python 3.x环境的步骤,包括安装系统依赖、下载和解压Python源码、编译安装、修改环境变量,以及常见安装错误的解决方案。
21 1
|
14天前
|
机器学习/深度学习 人工智能 搜索推荐
构建未来:移动应用开发与操作系统的融合之旅
【9月更文挑战第23天】在这个数字时代,移动设备已成为我们日常生活不可或缺的一部分。随着技术的进步,移动应用开发和移动操作系统不断进化,它们相互之间的界限开始模糊。本文将探讨移动应用开发的最新趋势、移动操作系统的创新以及这两者如何共同塑造我们的数字未来。通过深入浅出的分析,我们将了解这些技术是如何影响我们的生活、工作和沟通方式的。让我们一起踏上这场探索移动技术未来的旅程,见证它们如何共同编织出一个更加互联、智能和响应迅速的世界。
30 1