mirror of
https://github.com/wez/wezterm.git
synced 2024-12-23 05:12:40 +03:00
x11: load XCursor.theme based cursors when available
This requires `xcb-util-image-devel` on fedora, not sure about debian or other systems so far. refs: #524
This commit is contained in:
parent
387579cfb4
commit
aceb4933a9
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -4690,6 +4690,7 @@ dependencies = [
|
|||||||
"cocoa",
|
"cocoa",
|
||||||
"core-foundation 0.7.0",
|
"core-foundation 0.7.0",
|
||||||
"core-graphics 0.19.2",
|
"core-graphics 0.19.2",
|
||||||
|
"dirs-next",
|
||||||
"euclid",
|
"euclid",
|
||||||
"filedescriptor",
|
"filedescriptor",
|
||||||
"gl_generator",
|
"gl_generator",
|
||||||
|
@ -94,6 +94,7 @@ brief notes about them may accumulate here.
|
|||||||
* Unix: adjust font-config patterns to also match "dual spacing" fonts such as [Iosevka Term](https://typeof.net/Iosevka/). Thanks to [Leiser](https://github.com/leiserfg)! [#446](https://github.com/wez/wezterm/pull/446)
|
* Unix: adjust font-config patterns to also match "dual spacing" fonts such as [Iosevka Term](https://typeof.net/Iosevka/). Thanks to [Leiser](https://github.com/leiserfg)! [#446](https://github.com/wez/wezterm/pull/446)
|
||||||
* New: Added [alternate_buffer_wheel_scroll_speed](config/lua/config/alternate_buffer_wheel_scroll_speed.md) option to control how many cursor key presses are generated by the mouse wheel when the alternate screen is active. The new default for this is a faster-than-previous-releases 3 lines per wheel tick. [#432](https://github.com/wez/wezterm/issues/432)
|
* New: Added [alternate_buffer_wheel_scroll_speed](config/lua/config/alternate_buffer_wheel_scroll_speed.md) option to control how many cursor key presses are generated by the mouse wheel when the alternate screen is active. The new default for this is a faster-than-previous-releases 3 lines per wheel tick. [#432](https://github.com/wez/wezterm/issues/432)
|
||||||
* macOS: Dead Keys are now processed even when `use_ime=false`. [More details in the docs](config/keys.md#macos-left-and-right-option-key). [#410](https://github.com/wez/wezterm/issues/410).
|
* macOS: Dead Keys are now processed even when `use_ime=false`. [More details in the docs](config/keys.md#macos-left-and-right-option-key). [#410](https://github.com/wez/wezterm/issues/410).
|
||||||
|
* X11: attempt to load cursors from the XCursor.theme resource specified on the root window [#524](https://github.com/wez/wezterm/issues/524)
|
||||||
|
|
||||||
### 20201101-103216-403d002d
|
### 20201101-103216-403d002d
|
||||||
|
|
||||||
|
1
get-deps
1
get-deps
@ -23,6 +23,7 @@ if test -e /etc/centos-release || test -e /etc/fedora-release; then
|
|||||||
wayland-devel \
|
wayland-devel \
|
||||||
mesa-libEGL-devel \
|
mesa-libEGL-devel \
|
||||||
xcb-util-keysyms-devel \
|
xcb-util-keysyms-devel \
|
||||||
|
xcb-util-image-devel \
|
||||||
xcb-util-wm-devel \
|
xcb-util-wm-devel \
|
||||||
rpm-build \
|
rpm-build \
|
||||||
redhat-lsb-core
|
redhat-lsb-core
|
||||||
|
@ -51,10 +51,11 @@ clipboard-win = "2.2"
|
|||||||
shared_library = "0.1"
|
shared_library = "0.1"
|
||||||
|
|
||||||
[target.'cfg(all(unix, not(target_os = "macos")))'.dependencies]
|
[target.'cfg(all(unix, not(target_os = "macos")))'.dependencies]
|
||||||
|
dirs-next = "2.0"
|
||||||
filedescriptor = { version="0.7", path = "../filedescriptor" }
|
filedescriptor = { version="0.7", path = "../filedescriptor" }
|
||||||
x11 = {version ="2.18", features = ["xlib_xcb"]}
|
x11 = {version ="2.18", features = ["xlib_xcb"]}
|
||||||
xcb = {version="0.9", features=["shm", "xkb", "xlib_xcb"]}
|
xcb = {version="0.9", features=["render", "shm", "xkb", "xlib_xcb"]}
|
||||||
xcb-util = { features = [ "cursor", "icccm", "ewmh", "keysyms", "shm"], version = "0.3" }
|
xcb-util = { features = [ "cursor", "image", "icccm", "ewmh", "keysyms", "shm"], version = "0.3" }
|
||||||
xkbcommon = { version = "0.5", features = ["x11", "wayland"], git="https://github.com/wez/xkbcommon-rs.git", rev="01a0a0cd5663405e6e4abb1ad3add9add1496f58"}
|
xkbcommon = { version = "0.5", features = ["x11", "wayland"], git="https://github.com/wez/xkbcommon-rs.git", rev="01a0a0cd5663405e6e4abb1ad3add9add1496f58"}
|
||||||
mio = "0.6"
|
mio = "0.6"
|
||||||
libc = "0.2"
|
libc = "0.2"
|
||||||
|
@ -29,6 +29,7 @@ pub struct XConnection {
|
|||||||
pub atom_targets: xcb::Atom,
|
pub atom_targets: xcb::Atom,
|
||||||
pub atom_clipboard: xcb::Atom,
|
pub atom_clipboard: xcb::Atom,
|
||||||
keysyms: *mut xcb_key_symbols_t,
|
keysyms: *mut xcb_key_symbols_t,
|
||||||
|
pub(crate) xrm: HashMap<String, String>,
|
||||||
pub(crate) windows: RefCell<HashMap<xcb::xproto::Window, Arc<Mutex<XWindowInner>>>>,
|
pub(crate) windows: RefCell<HashMap<xcb::xproto::Window, Arc<Mutex<XWindowInner>>>>,
|
||||||
should_terminate: RefCell<bool>,
|
should_terminate: RefCell<bool>,
|
||||||
pub(crate) shm_available: bool,
|
pub(crate) shm_available: bool,
|
||||||
@ -391,11 +392,15 @@ impl XConnection {
|
|||||||
|
|
||||||
let root = screen.root();
|
let root = screen.root();
|
||||||
|
|
||||||
|
let xrm =
|
||||||
|
crate::x11::xrm::parse_root_resource_manager(&conn, root).unwrap_or(HashMap::new());
|
||||||
|
|
||||||
let conn = XConnection {
|
let conn = XConnection {
|
||||||
conn,
|
conn,
|
||||||
cursor_font_id,
|
cursor_font_id,
|
||||||
screen_num,
|
screen_num,
|
||||||
root,
|
root,
|
||||||
|
xrm,
|
||||||
atom_protocols,
|
atom_protocols,
|
||||||
atom_clipboard,
|
atom_clipboard,
|
||||||
atom_delete,
|
atom_delete,
|
||||||
|
@ -1,6 +1,12 @@
|
|||||||
use crate::x11::XConnection;
|
use crate::x11::XConnection;
|
||||||
use crate::MouseCursor;
|
use crate::MouseCursor;
|
||||||
|
use anyhow::{ensure, Context};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use std::convert::TryInto;
|
||||||
|
use std::ffi::OsString;
|
||||||
|
use std::io::prelude::*;
|
||||||
|
use std::io::SeekFrom;
|
||||||
|
use std::path::PathBuf;
|
||||||
use std::rc::{Rc, Weak};
|
use std::rc::{Rc, Weak};
|
||||||
use xcb::ffi::xcb_cursor_t;
|
use xcb::ffi::xcb_cursor_t;
|
||||||
|
|
||||||
@ -21,14 +27,113 @@ pub struct CursorInfo {
|
|||||||
cursors: HashMap<Option<MouseCursor>, XcbCursor>,
|
cursors: HashMap<Option<MouseCursor>, XcbCursor>,
|
||||||
cursor: Option<MouseCursor>,
|
cursor: Option<MouseCursor>,
|
||||||
conn: Weak<XConnection>,
|
conn: Weak<XConnection>,
|
||||||
|
size: Option<u32>,
|
||||||
|
theme: Option<String>,
|
||||||
|
icon_path: Vec<PathBuf>,
|
||||||
|
pict_format_id: Option<xcb::render::Pictformat>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn icon_path() -> Vec<PathBuf> {
|
||||||
|
let path = std::env::var_os("XCURSOR_PATH").unwrap_or_else(|| {
|
||||||
|
OsString::from("~/.icons:/usr/share/icons:/usr/share/pixmaps:/usr/X11R6/lib/X11/icons")
|
||||||
|
});
|
||||||
|
|
||||||
|
fn tilde_expand(p: PathBuf) -> PathBuf {
|
||||||
|
match p.to_str() {
|
||||||
|
Some(s) => {
|
||||||
|
if s.starts_with("~/") {
|
||||||
|
if let Some(home) = dirs_next::home_dir() {
|
||||||
|
home.join(&s[2..])
|
||||||
|
} else {
|
||||||
|
p.into()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
p.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => p.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::env::split_paths(&path).map(tilde_expand).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cursor_size(map: &HashMap<String, String>) -> u32 {
|
||||||
|
if let Ok(size) = std::env::var("XCURSOR_SIZE") {
|
||||||
|
if let Ok(size) = size.parse::<u32>() {
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(size) = map.get("Xcursor.size") {
|
||||||
|
if let Ok(size) = size.parse::<u32>() {
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(dpi) = map.get("Xft.dpi") {
|
||||||
|
if let Ok(dpi) = dpi.parse::<u32>() {
|
||||||
|
return dpi * 16 / 72;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Probably a good default?
|
||||||
|
24
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CursorInfo {
|
impl CursorInfo {
|
||||||
pub fn new(conn: Weak<XConnection>) -> Self {
|
pub fn new(conn: &Rc<XConnection>) -> Self {
|
||||||
|
let mut size = None;
|
||||||
|
let mut theme = None;
|
||||||
|
let mut pict_format_id = None;
|
||||||
|
// If we know the theme to use, then we need the render extension
|
||||||
|
// if we are to be able to load the cursor
|
||||||
|
let has_render = unsafe {
|
||||||
|
conn.get_extension_data(&mut xcb::ffi::render::xcb_render_id)
|
||||||
|
.map_or(false, |ext| ext.present())
|
||||||
|
};
|
||||||
|
if has_render {
|
||||||
|
if let Ok(vers) = xcb::render::query_version(
|
||||||
|
conn.conn(),
|
||||||
|
xcb::ffi::render::XCB_RENDER_MAJOR_VERSION,
|
||||||
|
xcb::ffi::render::XCB_RENDER_MINOR_VERSION,
|
||||||
|
)
|
||||||
|
.get_reply()
|
||||||
|
{
|
||||||
|
// 0.5 and later have the required support
|
||||||
|
if (vers.major_version(), vers.minor_version()) >= (0, 5) {
|
||||||
|
size.replace(cursor_size(&conn.xrm));
|
||||||
|
theme = conn.xrm.get("Xcursor.theme").cloned();
|
||||||
|
|
||||||
|
// Locate the Pictformat corresponding to ARGB32
|
||||||
|
if let Ok(formats) = xcb::render::query_pict_formats(conn.conn()).get_reply() {
|
||||||
|
for fmt in formats.formats() {
|
||||||
|
if fmt.depth() == 32 {
|
||||||
|
let direct = fmt.direct();
|
||||||
|
if direct.alpha_shift() == 24
|
||||||
|
&& direct.red_shift() == 16
|
||||||
|
&& direct.green_shift() == 8
|
||||||
|
&& direct.blue_shift() == 0
|
||||||
|
{
|
||||||
|
pict_format_id.replace(fmt.id());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let icon_path = icon_path();
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
cursors: HashMap::new(),
|
cursors: HashMap::new(),
|
||||||
cursor: None,
|
cursor: None,
|
||||||
conn,
|
conn: Rc::downgrade(conn),
|
||||||
|
size,
|
||||||
|
theme,
|
||||||
|
icon_path,
|
||||||
|
pict_format_id,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,43 +154,10 @@ impl CursorInfo {
|
|||||||
|
|
||||||
let cursor_id = match self.cursors.get(&cursor) {
|
let cursor_id = match self.cursors.get(&cursor) {
|
||||||
Some(cursor) => cursor.id,
|
Some(cursor) => cursor.id,
|
||||||
None => {
|
None => match self.load_themed(&conn, cursor) {
|
||||||
let id_no = match cursor.unwrap_or(MouseCursor::Arrow) {
|
Some(c) => c,
|
||||||
// `/usr/include/X11/cursorfont.h`
|
None => self.load_basic(&conn, cursor),
|
||||||
// <https://docs.rs/xcb-util/0.3.0/src/xcb_util/cursor.rs.html>
|
},
|
||||||
MouseCursor::Arrow => xcb_util::cursor::TOP_LEFT_ARROW,
|
|
||||||
MouseCursor::Hand => xcb_util::cursor::HAND1,
|
|
||||||
MouseCursor::Text => xcb_util::cursor::XTERM,
|
|
||||||
MouseCursor::SizeUpDown => xcb_util::cursor::SB_V_DOUBLE_ARROW,
|
|
||||||
MouseCursor::SizeLeftRight => xcb_util::cursor::SB_H_DOUBLE_ARROW,
|
|
||||||
};
|
|
||||||
|
|
||||||
let cursor_id: xcb::ffi::xcb_cursor_t = conn.generate_id();
|
|
||||||
xcb::create_glyph_cursor(
|
|
||||||
&conn,
|
|
||||||
cursor_id,
|
|
||||||
conn.cursor_font_id,
|
|
||||||
conn.cursor_font_id,
|
|
||||||
id_no,
|
|
||||||
id_no + 1,
|
|
||||||
0xffff,
|
|
||||||
0xffff,
|
|
||||||
0xffff,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
);
|
|
||||||
|
|
||||||
self.cursors.insert(
|
|
||||||
cursor,
|
|
||||||
XcbCursor {
|
|
||||||
id: cursor_id,
|
|
||||||
conn: Rc::downgrade(&conn),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
cursor_id
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
xcb::change_window_attributes(&conn, window_id, &[(xcb::ffi::XCB_CW_CURSOR, cursor_id)]);
|
xcb::change_window_attributes(&conn, window_id, &[(xcb::ffi::XCB_CW_CURSOR, cursor_id)]);
|
||||||
@ -94,4 +166,309 @@ impl CursorInfo {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn load_themed(&mut self, conn: &Rc<XConnection>, cursor: Option<MouseCursor>) -> Option<u32> {
|
||||||
|
let theme = self.theme.as_ref()?;
|
||||||
|
if self.pict_format_id.is_none() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let name = match cursor.unwrap_or(MouseCursor::Arrow) {
|
||||||
|
MouseCursor::Arrow => "top_left_arrow",
|
||||||
|
MouseCursor::Hand => "hand2",
|
||||||
|
MouseCursor::Text => "xterm",
|
||||||
|
MouseCursor::SizeUpDown => "sb_v_double_arrow",
|
||||||
|
MouseCursor::SizeLeftRight => "sb_h_double_arrow",
|
||||||
|
};
|
||||||
|
|
||||||
|
for dir in &self.icon_path {
|
||||||
|
let candidate = dir.join(theme).join("cursors").join(name);
|
||||||
|
if let Ok(file) = std::fs::File::open(&candidate) {
|
||||||
|
match self.parse_cursor_file(conn, file) {
|
||||||
|
Ok(cursor_id) => {
|
||||||
|
self.cursors.insert(
|
||||||
|
cursor,
|
||||||
|
XcbCursor {
|
||||||
|
id: cursor_id,
|
||||||
|
conn: Rc::downgrade(&conn),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return Some(cursor_id);
|
||||||
|
}
|
||||||
|
Err(err) => log::error!("{:#}", err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_basic(&mut self, conn: &Rc<XConnection>, cursor: Option<MouseCursor>) -> u32 {
|
||||||
|
let id_no = match cursor.unwrap_or(MouseCursor::Arrow) {
|
||||||
|
// `/usr/include/X11/cursorfont.h`
|
||||||
|
// <https://docs.rs/xcb-util/0.3.0/src/xcb_util/cursor.rs.html>
|
||||||
|
MouseCursor::Arrow => xcb_util::cursor::TOP_LEFT_ARROW,
|
||||||
|
MouseCursor::Hand => xcb_util::cursor::HAND1,
|
||||||
|
MouseCursor::Text => xcb_util::cursor::XTERM,
|
||||||
|
MouseCursor::SizeUpDown => xcb_util::cursor::SB_V_DOUBLE_ARROW,
|
||||||
|
MouseCursor::SizeLeftRight => xcb_util::cursor::SB_H_DOUBLE_ARROW,
|
||||||
|
};
|
||||||
|
|
||||||
|
let cursor_id: xcb::ffi::xcb_cursor_t = conn.generate_id();
|
||||||
|
xcb::create_glyph_cursor(
|
||||||
|
&conn,
|
||||||
|
cursor_id,
|
||||||
|
conn.cursor_font_id,
|
||||||
|
conn.cursor_font_id,
|
||||||
|
id_no,
|
||||||
|
id_no + 1,
|
||||||
|
0xffff,
|
||||||
|
0xffff,
|
||||||
|
0xffff,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
|
||||||
|
self.cursors.insert(
|
||||||
|
cursor,
|
||||||
|
XcbCursor {
|
||||||
|
id: cursor_id,
|
||||||
|
conn: Rc::downgrade(&conn),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
cursor_id
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_cursor_file(
|
||||||
|
&self,
|
||||||
|
conn: &Rc<XConnection>,
|
||||||
|
mut file: std::fs::File,
|
||||||
|
) -> anyhow::Result<u32> {
|
||||||
|
/* See: <https://cgit.freedesktop.org/xcb/util-cursor/tree/cursor/load_cursor.c>
|
||||||
|
*
|
||||||
|
* Cursor files start with a header. The header
|
||||||
|
* contains a magic number, a version number and a
|
||||||
|
* table of contents which has type and offset information
|
||||||
|
* for the remaining tables in the file.
|
||||||
|
*
|
||||||
|
* File minor versions increment for compatible changes
|
||||||
|
* File major versions increment for incompatible changes (never, we hope)
|
||||||
|
*
|
||||||
|
* Chunks of the same type are always upward compatible. Incompatible
|
||||||
|
* changes are made with new chunk types; the old data can remain under
|
||||||
|
* the old type. Upward compatible changes can add header data as the
|
||||||
|
* header lengths are specified in the file.
|
||||||
|
*
|
||||||
|
* File:
|
||||||
|
* FileHeader
|
||||||
|
* LISTofChunk
|
||||||
|
*
|
||||||
|
* FileHeader:
|
||||||
|
* CARD32 magic magic number
|
||||||
|
* CARD32 header bytes in file header
|
||||||
|
* CARD32 version file version
|
||||||
|
* CARD32 ntoc number of toc entries
|
||||||
|
* LISTofFileToc toc table of contents
|
||||||
|
*
|
||||||
|
* FileToc:
|
||||||
|
* CARD32 type entry type
|
||||||
|
* CARD32 subtype entry subtype (size for images)
|
||||||
|
* CARD32 position absolute file position
|
||||||
|
*/
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct FileHeader {
|
||||||
|
magic: u32,
|
||||||
|
header: u32,
|
||||||
|
version: u32,
|
||||||
|
ntoc: u32,
|
||||||
|
}
|
||||||
|
const MAGIC: u32 = 0x72756358;
|
||||||
|
const IMAGE_TYPE: u32 = 0xfffd0002;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct Toc {
|
||||||
|
type_: u32,
|
||||||
|
subtype: u32,
|
||||||
|
position: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read a u32 that is stored in little endian format,
|
||||||
|
/// return in host byte order
|
||||||
|
fn read_u32(r: &mut dyn Read) -> anyhow::Result<u32> {
|
||||||
|
let mut u32buf = [0u8; 4];
|
||||||
|
r.read_exact(&mut u32buf)?;
|
||||||
|
Ok(u32::from_le_bytes(u32buf))
|
||||||
|
}
|
||||||
|
|
||||||
|
let header = FileHeader {
|
||||||
|
magic: read_u32(&mut file)?,
|
||||||
|
header: read_u32(&mut file)?,
|
||||||
|
version: read_u32(&mut file)?,
|
||||||
|
ntoc: read_u32(&mut file)?,
|
||||||
|
};
|
||||||
|
ensure!(
|
||||||
|
header.magic == MAGIC,
|
||||||
|
"magic number doesn't match 0x{:x} != expected 0x{:x}",
|
||||||
|
header.magic,
|
||||||
|
MAGIC
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut toc = vec![];
|
||||||
|
for _ in 0..header.ntoc {
|
||||||
|
toc.push(Toc {
|
||||||
|
type_: read_u32(&mut file)?,
|
||||||
|
subtype: read_u32(&mut file)?,
|
||||||
|
position: read_u32(&mut file)?,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ensure!(!toc.is_empty(), "no images are present");
|
||||||
|
|
||||||
|
let size = self.size.unwrap_or(24) as isize;
|
||||||
|
let mut best = None;
|
||||||
|
for item in &toc {
|
||||||
|
if item.type_ != IMAGE_TYPE {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let distance = ((item.subtype as isize) - size).abs();
|
||||||
|
match best.take() {
|
||||||
|
None => {
|
||||||
|
best.replace((item, distance));
|
||||||
|
}
|
||||||
|
Some((other_item, other_dist)) => {
|
||||||
|
best.replace(if distance < other_dist {
|
||||||
|
(item, distance)
|
||||||
|
} else {
|
||||||
|
(other_item, other_dist)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let item = best
|
||||||
|
.take()
|
||||||
|
.ok_or_else(|| anyhow::anyhow!("no matching images"))?
|
||||||
|
.0;
|
||||||
|
|
||||||
|
file.seek(SeekFrom::Start(item.position.into()))?;
|
||||||
|
|
||||||
|
let _chunk_header = read_u32(&mut file)?;
|
||||||
|
let chunk_type = read_u32(&mut file)?;
|
||||||
|
let chunk_subtype = read_u32(&mut file)?;
|
||||||
|
let _chunk_version = read_u32(&mut file)?;
|
||||||
|
|
||||||
|
ensure!(
|
||||||
|
chunk_type == item.type_,
|
||||||
|
"chunk_type {:x} != item.type_ {:x}",
|
||||||
|
chunk_type,
|
||||||
|
item.type_
|
||||||
|
);
|
||||||
|
ensure!(
|
||||||
|
chunk_subtype == item.subtype,
|
||||||
|
"chunk_subtype {:x} != item.subtype {:x}",
|
||||||
|
chunk_subtype,
|
||||||
|
item.subtype
|
||||||
|
);
|
||||||
|
|
||||||
|
let width = read_u32(&mut file)?;
|
||||||
|
let height = read_u32(&mut file)?;
|
||||||
|
let xhot = read_u32(&mut file)?;
|
||||||
|
let yhot = read_u32(&mut file)?;
|
||||||
|
let _delay = read_u32(&mut file)?;
|
||||||
|
|
||||||
|
let num_pixels = (width as usize) * (height as usize);
|
||||||
|
ensure!(
|
||||||
|
num_pixels < u32::max_value() as usize,
|
||||||
|
"cursor image is larger than fits in u32"
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut pixels = vec![0u8; num_pixels * 4];
|
||||||
|
file.read_exact(&mut pixels)?;
|
||||||
|
|
||||||
|
// The data is all little endian; convert to host order
|
||||||
|
for chunk in pixels.chunks_exact_mut(4) {
|
||||||
|
let mut data = [0u8; 4];
|
||||||
|
data.copy_from_slice(chunk);
|
||||||
|
let le = u32::from_le_bytes(data);
|
||||||
|
data = le.to_le_bytes();
|
||||||
|
chunk.copy_from_slice(&data);
|
||||||
|
}
|
||||||
|
|
||||||
|
let image = unsafe {
|
||||||
|
xcb_util::ffi::image::xcb_image_create_native(
|
||||||
|
conn.conn().get_raw_conn(),
|
||||||
|
width.try_into()?,
|
||||||
|
height.try_into()?,
|
||||||
|
xcb::xproto::IMAGE_FORMAT_Z_PIXMAP,
|
||||||
|
32,
|
||||||
|
std::ptr::null_mut(),
|
||||||
|
pixels.len() as u32,
|
||||||
|
pixels.as_mut_ptr(),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
ensure!(!image.is_null(), "failed to create native image");
|
||||||
|
|
||||||
|
let pixmap = conn.generate_id();
|
||||||
|
xcb::xproto::create_pixmap_checked(
|
||||||
|
conn,
|
||||||
|
32,
|
||||||
|
pixmap,
|
||||||
|
conn.root,
|
||||||
|
width as u16,
|
||||||
|
height as u16,
|
||||||
|
)
|
||||||
|
.request_check()
|
||||||
|
.context("create_pixmap")?;
|
||||||
|
|
||||||
|
let gc = conn.generate_id();
|
||||||
|
xcb::create_gc(conn.conn(), gc, pixmap, &[]);
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
xcb_util::ffi::image::xcb_image_put(
|
||||||
|
conn.conn().get_raw_conn(),
|
||||||
|
pixmap,
|
||||||
|
gc,
|
||||||
|
image,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
xcb::free_gc(conn.conn(), gc);
|
||||||
|
|
||||||
|
let pic = conn.generate_id();
|
||||||
|
xcb::render::create_picture_checked(
|
||||||
|
conn.conn(),
|
||||||
|
pic,
|
||||||
|
pixmap,
|
||||||
|
self.pict_format_id.unwrap(),
|
||||||
|
&[],
|
||||||
|
)
|
||||||
|
.request_check()
|
||||||
|
.context("create_picture")?;
|
||||||
|
|
||||||
|
xcb::xproto::free_pixmap(conn.conn(), pixmap);
|
||||||
|
|
||||||
|
let cursor_id: xcb::ffi::xcb_cursor_t = conn.generate_id();
|
||||||
|
xcb::render::create_cursor_checked(
|
||||||
|
conn.conn(),
|
||||||
|
cursor_id,
|
||||||
|
pic,
|
||||||
|
xhot.try_into()?,
|
||||||
|
yhot.try_into()?,
|
||||||
|
)
|
||||||
|
.request_check()
|
||||||
|
.context("create_cursor")?;
|
||||||
|
|
||||||
|
xcb::render::free_picture(conn.conn(), pic);
|
||||||
|
unsafe {
|
||||||
|
xcb_util::ffi::image::xcb_image_destroy(image);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(cursor_id)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ pub mod connection;
|
|||||||
pub mod cursor;
|
pub mod cursor;
|
||||||
pub mod keyboard;
|
pub mod keyboard;
|
||||||
pub mod window;
|
pub mod window;
|
||||||
|
pub mod xrm;
|
||||||
|
|
||||||
pub use self::window::*;
|
pub use self::window::*;
|
||||||
pub use bitmap::*;
|
pub use bitmap::*;
|
||||||
|
@ -718,7 +718,7 @@ impl XWindow {
|
|||||||
expose: VecDeque::new(),
|
expose: VecDeque::new(),
|
||||||
paint_all: true,
|
paint_all: true,
|
||||||
copy_and_paste: CopyAndPaste::default(),
|
copy_and_paste: CopyAndPaste::default(),
|
||||||
cursors: CursorInfo::new(Rc::downgrade(&conn)),
|
cursors: CursorInfo::new(&conn),
|
||||||
gl_state: None,
|
gl_state: None,
|
||||||
config: Arc::clone(&config),
|
config: Arc::clone(&config),
|
||||||
}))
|
}))
|
||||||
|
34
window/src/os/x11/xrm.rs
Normal file
34
window/src/os/x11/xrm.rs
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
/// Parses:
|
||||||
|
/// $ xprop -root | grep RESOURCE_MANAGER
|
||||||
|
/// RESOURCE_MANAGER(STRING) = "Xft.dpi:\t96\nXft.hinting:\t1\nXft.hintstyle:\thintslight\nXft.antialias:\t1\nXft.rgba:\tnone\nXcursor.size:\t24\nXcursor.theme:\tAdwaita\n"
|
||||||
|
pub fn parse_root_resource_manager(
|
||||||
|
conn: &xcb::Connection,
|
||||||
|
root: xcb::xproto::Window,
|
||||||
|
) -> anyhow::Result<HashMap<String, String>> {
|
||||||
|
let reply = xcb::xproto::get_property(
|
||||||
|
conn,
|
||||||
|
false,
|
||||||
|
root,
|
||||||
|
xcb::ffi::XCB_ATOM_RESOURCE_MANAGER,
|
||||||
|
xcb::xproto::ATOM_STRING,
|
||||||
|
0,
|
||||||
|
1024 * 1024,
|
||||||
|
)
|
||||||
|
.get_reply()?;
|
||||||
|
|
||||||
|
let text = String::from_utf8_lossy(reply.value::<u8>());
|
||||||
|
let mut map = HashMap::new();
|
||||||
|
for line in text.split('\n') {
|
||||||
|
if let Some(colon) = line.find(':') {
|
||||||
|
let (key, value) = line.split_at(colon);
|
||||||
|
let key = key.trim();
|
||||||
|
let value = value[1..].trim();
|
||||||
|
|
||||||
|
map.insert(key.to_string(), value.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(map)
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user