[software development] Wayland学习之——使用Rust语言编写第一个Wayland程序!
Tofloor
poster avatar
PurestAsh
deepin
2024-10-19 14:52
Author

众所周知:wayland程序和服务端的compositor沟通用的是unix socket。

所以我们第1步就是连接到这个socket。

连socket成功之后,服务端会向我们推送他所有的全局对象列表。

然后我们可以挑感兴趣的使用。

这里有几个是我们必须要用到的。

1.wl_compositor对象:表示的就是服务端的compositor。通过它,我们申请一张“纸”

2.wl_surface对象:也就是所谓的“纸”,这张纸是白的,啥也没有。

3.wl_buffer对象:就像一桶"油漆",里面装满了颜色。我们可以把油漆撒到纸上。

4.wl_shm对象:共享内存对象。这是服务端提供的功能,通过它我们可以申请一个"油漆桶"-wl_buffer。并且这桶还是和client共享的。

接下来我们还需要一些依赖,来方便开发:

  1. smithay-client-toolkit: 这个包是对wayland-client的封装。用他我们可以快速获取全局对象。
  2. nix: 这个包是对linux系统调用的封装。通过它我们可以申请一段共享内存。
  3. memmap2:这个包可以用来进行内存映射,把我们前面申请到的内存映射进来用。

这里我们使用wl_buffer的xrgb8888格式来表示一个像素:意思就是8*4=32位。32位表示一个像素,也就是4字节。

注意wl_buffer的stride参数,它表示显示一行要有多少各字节。所以stride应该 = 1行的像素个数 * 4字节。

use std::{env, ffi::CString, i32, os::fd::AsFd};

use nix::sys::memfd;
use smithay_client_toolkit::reexports::{
    client::{
        globals::{registry_queue_init, GlobalListContents},
        protocol::{
            wl_buffer::{self, WlBuffer},
            wl_compositor::{self, WlCompositor},
            wl_registry,
            wl_shm::{self, WlShm},
            wl_shm_pool::{self, WlShmPool},
            wl_surface::{self, WlSurface},
        },
        Connection, Dispatch, QueueHandle,
    },
    protocols::xdg::shell::client::{
        xdg_surface::{self, XdgSurface},
        xdg_toplevel::{self, XdgToplevel},
        xdg_wm_base::{self, XdgWmBase},
    },
};

struct MyApp {
    exit: bool,
}

struct MyUserData;

const WIDTH: i32 = 600;
const HEIGHT: i32 = 400;
const STRIDE: i32 = WIDTH * 4;
const STORE_SIZE: i32 = WIDTH * HEIGHT * 2 * 4;

impl Dispatch for MyApp {
    fn event(
        _state: &mut MyApp,
        _proxy: &wl_registry::WlRegistry,
        _event: wl_registry::Event,
        _data: &GlobalListContents,
        _conn: &Connection,
        _qhandle: &QueueHandle,
    ) {
    }
}

impl Dispatch for MyApp {
    fn event(
        _state: &mut Self,
        _proxy: &WlCompositor,
        _event: wl_compositor::Event,
        _data: &MyUserData,
        _conn: &Connection,
        _qhandle: &QueueHandle,
    ) {
    }
}

impl Dispatch for MyApp {
    fn event(
        _state: &mut Self,
        _proxy: &WlSurface,
        _event: wl_surface::Event,
        _data: &MyUserData,
        _conn: &Connection,
        _qhandle: &QueueHandle,
    ) {
    }
}

impl Dispatch for MyApp {
    fn event(
        _state: &mut Self,
        _proxy: &WlShm,
        _event: wl_shm::Event,
        _data: &MyUserData,
        _conn: &Connection,
        _qhandle: &QueueHandle,
    ) {
    }
}

impl Dispatch for MyApp {
    fn event(
        _state: &mut Self,
        _proxy: &WlShmPool,
        _event: wl_shm_pool::Event,
        _data: &MyUserData,
        _conn: &Connection,
        _qhandle: &QueueHandle,
    ) {
    }
}

impl Dispatch for MyApp {
    fn event(
        _state: &mut Self,
        _proxy: &WlBuffer,
        _event: wl_buffer::Event,
        _data: &MyUserData,
        _conn: &Connection,
        _qhandle: &QueueHandle,
    ) {
    }
}

impl Dispatch for MyApp {
    fn event(
        _state: &mut Self,
        _proxy: &XdgWmBase,
        _event: xdg_wm_base::Event,
        _data: &MyUserData,
        _conn: &Connection,
        _qhandle: &QueueHandle,
    ) {
    }
}

impl Dispatch for MyApp {
    fn event(
        _state: &mut Self,
        _proxy: &XdgSurface,
        _event: xdg_surface::Event,
        _data: &MyUserData,
        _conn: &Connection,
        _qhandle: &QueueHandle,
    ) {
    }
}

impl Dispatch for MyApp {
    fn event(
        _state: &mut Self,
        _proxy: &XdgToplevel,
        _event: xdg_toplevel::Event,
        _data: &MyUserData,
        _conn: &Connection,
        _qhandle: &QueueHandle,
    ) {
    }
}

fn main() {
    //设置环境变量,你决定要连接到哪个wayland compositor
    //默认是wayland-0 .具体文件放在/run/user/1000/下
    env::set_var("WAYLAND_DISPLAY", "wayland-0");

    //连接到wayland服务器
    let conn = Connection::connect_to_env().expect("connect failed");

    //这个方法会获取wl_display,然后发送get_registry请求,然后获取所有的全局接口
    let (glist, mut event_queue) = registry_queue_init::(&conn).unwrap();

    for ele in glist.contents().clone_list() {
        println!("{},{},{}", ele.name, ele.interface, ele.version);
    }
    //绑定到全局对象wl_compositor
    let wl_compositor: WlCompositor = glist
        .bind(&event_queue.handle(), 1..=6, MyUserData)
        .unwrap();

    //申请一张纸
    let wl_surface = wl_compositor.create_surface(&event_queue.handle(), MyUserData);

    //把这张纸转换成xdg_surface,这样才能在常见的桌面环境下显示
    let xdg_wm_base: XdgWmBase = glist
        .bind(&event_queue.handle(), 1..=6, MyUserData)
        .unwrap();
    let xdg_surface = xdg_wm_base.get_xdg_surface(&wl_surface, &event_queue.handle(), MyUserData);
    //让他显示到最上层,不然我们啥也看不到。
    let xdg_toplevel = xdg_surface.get_toplevel(&event_queue.handle(), MyUserData);
    xdg_toplevel.set_title(String::from("test"));

    //获得wl_shm全局对象
    let wl_shm: WlShm = glist
        .bind(&event_queue.handle(), 1..=1, MyUserData)
        .unwrap();

    //申请一段共享内存
    let fd = memfd::memfd_create(
        CString::new("shm_file").unwrap().as_c_str(),
        memfd::MemFdCreateFlag::empty(),
    )
    .unwrap();
    //为共享内存分配大小
    nix::unistd::ftruncate(fd.try_clone().unwrap(), STORE_SIZE.into()).unwrap();

    //通知Server端,我现在要用这个文件放像素(Server端也同样会做内存映射)
    let wl_shm_pool = wl_shm.create_pool(fd.as_fd(), STORE_SIZE, &event_queue.handle(), MyUserData);
    //通知Server端,我要现在要在上面存一个buffer,同时告诉他buffer的大小,存在里面字节的像素格式
    //xrgb8888一共是32位,也就是4字节,一个像素就是4字节,注意这个STRIDE值
    let wl_buffer = wl_shm_pool.create_buffer(
        0,
        WIDTH,
        HEIGHT,
        STRIDE,
        wl_shm::Format::Xrgb8888,
        &event_queue.handle(),
        MyUserData,
    );
    //映射这段共享内存到客户端
    let mut mmap = unsafe {
        memmap2::MmapOptions::new()
            .len(STORE_SIZE as usize)
            .map_mut(&fd)
            .unwrap()
    };
    //开始画画
    //这个内存映射本身就相当于一个8位为一个字节的数组
    //但是我们的一个像素是32位的,所以我们要把这8位字节的对齐到32位。
    //如果这个字节数组不是32位的整数倍,pre和end就会多出来几个字节。
    let (_pre, middle, _end) = unsafe { mmap.align_to_mut::() };
    //绿+蓝
    middle.fill(0xFF00FFFF);

    //开始倾倒:把这桶buffer油漆放到这张surface纸上
    wl_surface.attach(Some(&wl_buffer), 0, 0);
    //告诉服务端这张纸需要更新
    wl_surface.damage(0, 0, i32::MAX, i32::MAX);
    //告诉Server端倾倒完成
    wl_surface.commit();

    let mut my_app = MyApp { exit: false };
    //利用wl_compistor创建一个wl_surface
    while !my_app.exit {
        event_queue.blocking_dispatch(&mut my_app).unwrap();
    }
}

然后cargo run !!

kwin下效果如下:

image.png

Reply Favorite View the author
All Replies
蔡EEPIN
deepin
2024-10-19 19:32
#1

不明觉厉confused

Reply View the author
小鱼贝壳
deepin
2024-10-20 00:15
#2

学习了

Reply View the author
放屁大王
deepin
2024-10-20 06:59
#3

like like 不错

Reply View the author
fiil
deepin
2024-10-20 09:30
#4

不错不错。接下来该做个Office了

Reply View the author
Oli
deepin
2024-10-21 00:38
#5

很通俗易懂了

Reply View the author
先秦淑女步
deepin
2024-10-21 01:30
#6

棒棒的~

Reply View the author
ggbond
deepin
2024-10-22 06:54
#7

hi joy

Reply View the author