From c8a64ab7525668754ccbd255b21f4509ea69cb6b Mon Sep 17 00:00:00 2001 From: Brent Westbrook <36778786+ntBre@users.noreply.github.com> Date: Sat, 7 Dec 2024 12:34:55 -0500 Subject: [PATCH] Delete UTF-8 decoding, switch to String (#40) * make rect take &mut Drw * make invert bool * use refs * allow clippy fix when dirty * more refs * font_getexts never used h * clean up some more unused unsafe * make charexists bool * utf8decode was only called with clen = UTF_SIZ * mostly working, but title is always `broken` in xephyr * initialize client name to avoid precondition violation * remove size argument to gettextprop * update screen size in xephyr * debugging missing emojis in xephyr * fix emojis by actually setting utf8codepoint * fix some characters in title * char::from is a little nicer, then give up for now --- Makefile | 4 +- src/config.rs | 67 +++++++----- src/drw.rs | 257 ++++++++++---------------------------------- src/handlers.rs | 9 +- src/key_handlers.rs | 9 +- src/layouts.rs | 7 +- src/lib.rs | 14 +-- src/main.rs | 117 +++++++++----------- src/state.rs | 7 +- src/tests.rs | 2 +- src/util.rs | 5 - 11 files changed, 168 insertions(+), 330 deletions(-) diff --git a/Makefile b/Makefile index 2ab12d0..a065e3b 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,7 @@ test: clippy_args := ifdef FIX - clippy_args += --fix + clippy_args += --fix --allow-dirty endif clippy: @@ -28,4 +28,4 @@ build: target/release/rwm .PHONY: build xephyr: - Xephyr :1 & DISPLAY=:1.0 cargo run && kill %1 + Xephyr :1 -screen 960x540 & DISPLAY=:1.0 cargo run && kill %1 diff --git a/src/config.rs b/src/config.rs index 3e509b8..b1411d2 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,7 +1,7 @@ use std::{ collections::HashMap, error::Error, - ffi::{c_float, c_int, c_uint, CStr, CString}, + ffi::{c_float, c_int, c_uint, CString}, path::Path, ptr::null, sync::LazyLock, @@ -41,8 +41,8 @@ impl Default for Config { resize_hints: false, lock_fullscreen: true, fonts: vec![c"monospace:size=10".into()], - tags: [c"1", c"2", c"3", c"4", c"5", c"6", c"7", c"8", c"9"] - .map(CString::from) + tags: ["1", "2", "3", "4", "5", "6", "7", "8", "9"] + .map(String::from) .to_vec(), colors: default_colors(), keys: default_keys().to_vec(), @@ -56,7 +56,7 @@ impl Default for Config { showsystray: SHOWSYSTRAY, buttons: BUTTONS.to_vec(), layouts: LAYOUTS.to_vec(), - scratchpadname: SCRATCHPADNAME.into(), + scratchpadname: SCRATCHPADNAME.to_string(), } } } @@ -88,7 +88,7 @@ pub struct Config { pub fonts: Vec, - pub tags: Vec, + pub tags: Vec, pub colors: [[CString; 3]; 2], @@ -118,7 +118,7 @@ pub struct Config { pub layouts: Vec, - pub scratchpadname: CString, + pub scratchpadname: String, } unsafe impl Send for Config {} @@ -181,7 +181,7 @@ fn get_rules(v: &mut HashMap) -> Result, FigError> { // NOTE each into_raw call is a leak, but these should live as long as the // program runs anyway - let maybe_string = |val: Value| -> Result<*const c_char, FigError> { + let maybe_cstring = |val: Value| -> Result<*const c_char, FigError> { if let Ok(Ok(s)) = String::try_from(val.clone()).map(CString::new) { Ok(s.into_raw()) } else if val.is_nil() { @@ -192,6 +192,17 @@ fn get_rules(v: &mut HashMap) -> Result, FigError> { } }; + let maybe_string = |val: Value| -> Result { + if let Ok(s) = String::try_from(val.clone()) { + Ok(s) + } else if val.is_nil() { + Ok(String::new()) + } else { + log::error!("expected Str or Nil"); + Err(FigError::Conversion) + } + }; + let mut ret = Vec::new(); for rule in rules { let rule: Vec = rule.try_into()?; @@ -200,8 +211,8 @@ fn get_rules(v: &mut HashMap) -> Result, FigError> { return err; } ret.push(Rule { - class: maybe_string(rule[0].clone())?, - instance: maybe_string(rule[1].clone())?, + class: maybe_cstring(rule[0].clone())?, + instance: maybe_cstring(rule[1].clone())?, title: maybe_string(rule[2].clone())?, tags: i64::try_from(rule[3].clone())? as u32, isfloating: rule[4].clone().try_into()?, @@ -280,9 +291,6 @@ fn get_layouts( } let symbol: String = layout[0].clone().try_into()?; - let symbol = CString::new(symbol).map_err(|_| FigError::Conversion)?; - // LEAK but okay since this should hang around for the whole program - let symbol = symbol.into_raw(); type F = fn(&mut State, *mut Monitor); let arrange = match &layout[1] { @@ -310,13 +318,17 @@ impl TryFrom for Config { val.try_into_int().map_err(|_| "unable to parse int") }; let bool = |val: fig::Value| val.try_into(); - let str_list = |val: fig::Value| -> Result, _> { + let cstr_list = |val: fig::Value| -> Result, _> { let strs: Vec = val.try_into()?; strs.into_iter() .map(CString::new) .collect::, _>>() .map_err(|_| Box::new(FigError::Conversion)) }; + let str_list = |val: fig::Value| -> Result, FigError> { + let strs: Vec = val.try_into()?; + Ok(strs.into_iter().map(String::from).collect()) + }; Ok(Self { borderpx: int(get(&mut v, "borderpx")?)? as c_uint, snap: int(get(&mut v, "snap")?)? as c_uint, @@ -326,7 +338,7 @@ impl TryFrom for Config { nmaster: int(get(&mut v, "nmaster")?)? as c_int, resize_hints: bool(get(&mut v, "resize_hints")?)?, lock_fullscreen: bool(get(&mut v, "lock_fullscreen")?)?, - fonts: str_list(get(&mut v, "fonts")?)?, + fonts: cstr_list(get(&mut v, "fonts")?)?, tags: str_list(get(&mut v, "tags")?)?, colors: get_colors(&mut v)?, keys: get_keys(&mut v)?, @@ -343,10 +355,7 @@ impl TryFrom for Config { showsystray: get(&mut v, "showsystray")?.try_into()?, buttons: get_buttons(&mut v)?, layouts: get_layouts(&mut v)?, - scratchpadname: CString::new(String::try_from(get( - &mut v, - "scratchpadname", - )?)?)?, + scratchpadname: String::try_from(get(&mut v, "scratchpadname")?)?, }) } } @@ -419,7 +428,7 @@ const RULES: [Rule; 3] = [ Rule { class: c"Gimp".as_ptr(), instance: null(), - title: null(), + title: String::new(), tags: 0, isfloating: true, isterminal: false, @@ -429,7 +438,7 @@ const RULES: [Rule; 3] = [ Rule { class: c"Firefox".as_ptr(), instance: null(), - title: null(), + title: String::new(), tags: 1 << 8, isfloating: false, isterminal: false, @@ -439,7 +448,7 @@ const RULES: [Rule; 3] = [ Rule { class: c"st-256color".as_ptr(), instance: null(), - title: null(), + title: String::new(), tags: 0, isfloating: false, isterminal: true, @@ -450,11 +459,13 @@ const RULES: [Rule; 3] = [ // layouts -const LAYOUTS: [Layout; 3] = [ - Layout { symbol: c"[]=".as_ptr(), arrange: Some(tile) }, - Layout { symbol: c"><>".as_ptr(), arrange: None }, - Layout { symbol: c"[M]".as_ptr(), arrange: Some(monocle) }, -]; +static LAYOUTS: LazyLock<[Layout; 3]> = LazyLock::new(|| { + [ + Layout { symbol: "[]=".to_string(), arrange: Some(tile) }, + Layout { symbol: "><>".to_string(), arrange: None }, + Layout { symbol: "[M]".to_string(), arrange: Some(monocle) }, + ] +}); // key definitions const MODKEY: c_uint = Mod4Mask; @@ -481,12 +492,12 @@ static DMENUCMD: LazyLock> = LazyLock::new(|| { }); static TERMCMD: LazyLock> = LazyLock::new(|| vec!["st".into()]); -const SCRATCHPADNAME: &CStr = c"scratchpad"; +const SCRATCHPADNAME: &str = "scratchpad"; static SCRATCHPADCMD: LazyLock> = LazyLock::new(|| { vec![ "st".into(), "-t".into(), - SCRATCHPADNAME.to_str().unwrap().to_owned(), + SCRATCHPADNAME.to_string(), "-g".into(), "120x34".into(), ] diff --git a/src/drw.rs b/src/drw.rs index 1e64af0..ab563d3 100644 --- a/src/drw.rs +++ b/src/drw.rs @@ -16,20 +16,11 @@ use x11::xlib::{ }; use crate::enums::Col; -use crate::util::between; use crate::util::die; use crate::Clr; use crate::Cursor as Cur; use crate::Window; -// defined in drw.c -const UTF_SIZ: usize = 4; -const UTF_INVALID: usize = 0xFFFD; -const UTFBYTE: [c_uchar; UTF_SIZ + 1] = [0x80, 0, 0xC0, 0xE0, 0xF0]; -const UTFMASK: [c_uchar; UTF_SIZ + 1] = [0xC0, 0x80, 0xE0, 0xF0, 0xF8]; -const UTFMIN: [c_long; UTF_SIZ + 1] = [0, 0, 0x80, 0x800, 0x10000]; -const UTFMAX: [c_long; UTF_SIZ + 1] = [0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF]; - // defined in /usr/include/fontconfig/fontconfig.h const FC_TRUE: i32 = 1; @@ -55,65 +46,6 @@ pub struct Drw { pub scheme: Vec, pub fonts: Vec, } - -fn utf8decodebyte(c: c_char, i: *mut usize) -> c_long { - unsafe { - *i = 0; - while *i < UTF_SIZ + 1 { - if c as c_uchar & UTFMASK[*i] == UTFBYTE[*i] { - return (c as c_uchar & !UTFMASK[*i]) as c_long; - } - *i += 1; - } - 0 - } -} - -fn utf8validate(u: *mut c_long, i: usize) -> usize { - unsafe { - if !between(*u, UTFMIN[i], UTFMAX[i]) || between(*u, 0xD800, 0xDFFF) { - *u = UTF_INVALID as c_long; - } - let mut i = 1; - while *u > UTFMAX[i] { - i += 1; - } - i - } -} - -fn utf8decode(c: *const i8, u: *mut c_long, clen: usize) -> usize { - unsafe { - *u = UTF_INVALID as c_long; - if clen == 0 { - return 0; - } - let mut len = 0; - let mut udecoded = utf8decodebyte(*c, &mut len); - if !between(len, 1, UTF_SIZ) { - return 1; - } - let mut i = 1; - let mut j = 1; - let mut type_ = 0; - while i < clen && j < len { - udecoded = (udecoded << 6) | utf8decodebyte(*c.add(i), &mut type_); - if type_ != 0 { - return j; - } - i += 1; - j += 1; - } - if j < len { - return 0; - } - *u = udecoded; - utf8validate(u, len); - - len - } -} - /// # Safety pub unsafe fn create( dpy: *mut Display, @@ -157,45 +89,33 @@ pub fn free(drw: &mut Drw) { } } -/// # Safety -pub unsafe fn rect( - drw: *mut Drw, +pub fn rect( + drw: &mut Drw, x: c_int, y: c_int, w: c_uint, h: c_uint, filled: c_int, - invert: c_int, + invert: bool, ) { + if drw.scheme.is_empty() { + return; + } unsafe { - if drw.is_null() || (*drw).scheme.is_empty() { - // TODO can this be &mut Drw? - return; - } xlib::XSetForeground( - (*drw).dpy, - (*drw).gc, - if invert != 0 { - (*drw).scheme[Col::Bg as usize].pixel - } else { - (*drw).scheme[Col::Fg as usize].pixel - }, + drw.dpy, + drw.gc, + drw.scheme + [if invert { Col::Bg as usize } else { Col::Fg as usize }] + .pixel, ); if filled != 0 { - xlib::XFillRectangle( - (*drw).dpy, - (*drw).drawable, - (*drw).gc, - x, - y, - w, - h, - ); + xlib::XFillRectangle(drw.dpy, drw.drawable, drw.gc, x, y, w, h); } else { xlib::XDrawRectangle( - (*drw).dpy, - (*drw).drawable, - (*drw).gc, + drw.dpy, + drw.drawable, + drw.gc, x, y, w - 1, @@ -205,8 +125,7 @@ pub unsafe fn rect( } } -/// # Safety -pub unsafe fn cur_create(drw: &Drw, shape: c_int) -> Cur { +pub fn cur_create(drw: &Drw, shape: c_int) -> Cur { unsafe { Cur { cursor: xlib::XCreateFontCursor(drw.dpy, shape as c_uint) } } } @@ -300,15 +219,15 @@ impl Drop for Fnt { } } -fn clr_create(drw: *const Drw, dest: *mut Clr, clrname: *const c_char) { - if drw.is_null() || dest.is_null() || clrname.is_null() { +fn clr_create(drw: &Drw, dest: *mut Clr, clrname: *const c_char) { + if dest.is_null() || clrname.is_null() { return; } unsafe { if xft::XftColorAllocName( - (*drw).dpy, - xlib::XDefaultVisual((*drw).dpy, (*drw).screen), - xlib::XDefaultColormap((*drw).dpy, (*drw).screen), + drw.dpy, + xlib::XDefaultVisual(drw.dpy, drw.screen), + xlib::XDefaultColormap(drw.dpy, drw.screen), clrname, dest, ) == 0 @@ -322,12 +241,12 @@ fn clr_create(drw: *const Drw, dest: *mut Clr, clrname: *const c_char) { } pub fn scm_create( - drw: *const Drw, + drw: &Drw, clrnames: &[CString], clrcount: usize, ) -> Vec { let mut ret = Vec::new(); - if drw.is_null() || clrnames.is_empty() || clrcount < 2 { + if clrnames.is_empty() || clrcount < 2 { return ret; } for clr in clrnames { @@ -341,9 +260,9 @@ pub fn scm_create( } /// # Safety -pub unsafe fn fontset_getwidth(drw: &mut Drw, text: *const c_char) -> c_uint { +pub unsafe fn fontset_getwidth(drw: &mut Drw, text: &str) -> c_uint { log::trace!("fontset_getwidth"); - if drw.fonts.is_empty() || text.is_null() { + if drw.fonts.is_empty() || text.is_empty() { return 0; } self::text(drw, 0, 0, 0, 0, 0, text, 0) as c_uint @@ -357,7 +276,7 @@ pub unsafe fn text( mut w: c_uint, h: c_uint, lpad: c_uint, - mut text: *const c_char, + mut text: &str, invert: c_int, ) -> c_int { // this function is very confusing and likely can be dramatically simplified @@ -366,7 +285,7 @@ pub unsafe fn text( unsafe { log::trace!( "text: {drw:?}, {x}, {y}, {w}, {h}, {lpad}, {:?}, {invert}", - std::ffi::CStr::from_ptr(text) + text ); let mut ty: c_int; let mut ellipsis_x: c_int = 0; @@ -396,7 +315,7 @@ pub unsafe fn text( let mut result: xft::FcResult = xft::FcResult::NoMatch; - let mut charexists: c_int = 0; + let mut charexists = false; let mut overflow: c_int = 0; // keep track of a couple codepoints for which we have no match @@ -410,7 +329,7 @@ pub unsafe fn text( static mut ELLIPSIS_WIDTH: c_uint = 0; if (render != 0 && (drw.scheme.is_empty() || w == 0)) - || text.is_null() + || text.is_empty() || drw.fonts.is_empty() { return 0; @@ -441,7 +360,7 @@ pub unsafe fn text( } if ELLIPSIS_WIDTH == 0 && render != 0 { - ELLIPSIS_WIDTH = fontset_getwidth(drw, c"...".as_ptr()); + ELLIPSIS_WIDTH = fontset_getwidth(drw, "..."); } usedfont = 0; log::trace!("text: entering loop"); @@ -449,7 +368,7 @@ pub unsafe fn text( ew = 0; ellipsis_len = 0; utf8strlen = 0; - utf8str = text; + utf8str = text.as_ptr().cast(); nextfont = None; // I believe this loop is just walking along the characters in text @@ -462,23 +381,22 @@ pub unsafe fn text( // `while(*text)` to `while !text.is_null()`, but we actually need // to check if we're at the null byte at the end of the string, NOT // if text is a null pointer - while *text != b'\0' as i8 { - utf8charlen = - utf8decode(text, &mut utf8codepoint, UTF_SIZ) as c_int; + for c in text.chars() { + utf8codepoint = c as i64; + utf8charlen = c.len_utf8() as i32; for (font_idx, curfont) in drw.fonts.iter().enumerate() { - charexists = (charexists != 0 + charexists = charexists || xft::XftCharExists( drw.dpy, curfont.xfont, utf8codepoint as u32, - ) != 0) as c_int; - if charexists != 0 { + ) != 0; + if charexists { font_getexts( curfont, - text, + text.as_ptr().cast(), utf8charlen as u32, &mut tmpw, - null_mut(), ); if ew + ELLIPSIS_WIDTH <= w { // keep track where the ellipsis still fits @@ -498,7 +416,7 @@ pub unsafe fn text( } } else if font_idx == usedfont { utf8strlen += utf8charlen; - text = text.add(utf8charlen as usize); + text = &text[utf8charlen as usize..]; ew += tmpw; } else { nextfont = Some(font_idx); @@ -508,12 +426,12 @@ pub unsafe fn text( } if overflow != 0 - || charexists == 0 + || !charexists || nextfont.is_some_and(|n| n < drw.fonts.len()) { break; } else { - charexists = 0; + charexists = false; } } // end while(*text) @@ -542,27 +460,18 @@ pub unsafe fn text( } if render != 0 && overflow != 0 { log::trace!("recursing for render != && overflow != 0"); - self::text( - drw, - ellipsis_x, - y, - ellipsis_w, - h, - 0, - c"...".as_ptr(), - invert, - ); + self::text(drw, ellipsis_x, y, ellipsis_w, h, 0, "...", invert); } - if *text == b'\0' as i8 || overflow != 0 { + if text.is_empty() || overflow != 0 { break; } else if nextfont.is_some_and(|n| n < drw.fonts.len()) { - charexists = 0; + charexists = false; usedfont = nextfont.unwrap(); } else { // regardless of whether or not a fallback font is found, the // character must be drawn - charexists = 1; + charexists = true; for i in 0..NOMATCHES_LEN { // avoid calling XftFontMatch if we know we won't find a @@ -654,13 +563,7 @@ pub unsafe fn text( } } -fn font_getexts( - font: *const Fnt, - text: *const i8, - len: u32, - w: *mut c_uint, - h: *mut c_uint, -) { +fn font_getexts(font: *const Fnt, text: *const i8, len: u32, w: &mut c_uint) { unsafe { if font.is_null() || text.is_null() { return; @@ -674,79 +577,37 @@ fn font_getexts( ext.as_mut_ptr(), ); let ext = ext.assume_init(); - if !w.is_null() { - *w = ext.xOff as u32; - } - if !h.is_null() { - *h = (*font).h; - } + *w = ext.xOff as u32; } } pub unsafe fn map( - drw: *mut Drw, + drw: &Drw, win: Window, x: c_int, y: c_int, w: c_uint, h: c_uint, ) { - if drw.is_null() { - return; - } unsafe { - xlib::XCopyArea( - (*drw).dpy, - (*drw).drawable, - win, - (*drw).gc, - x, - y, - w, - h, - x, - y, - ); - xlib::XSync((*drw).dpy, False); + xlib::XCopyArea(drw.dpy, drw.drawable, win, drw.gc, x, y, w, h, x, y); + xlib::XSync(drw.dpy, False); } } -pub unsafe fn resize(drw: *mut Drw, w: c_uint, h: c_uint) { +pub fn resize(drw: &mut Drw, w: c_uint, h: c_uint) { unsafe { - if drw.is_null() { - return; + drw.w = w; + drw.h = h; + if drw.drawable != 0 { + xlib::XFreePixmap(drw.dpy, drw.drawable); } - (*drw).w = w; - (*drw).h = h; - if (*drw).drawable != 0 { - xlib::XFreePixmap((*drw).dpy, (*drw).drawable); - } - (*drw).drawable = xlib::XCreatePixmap( - (*drw).dpy, - (*drw).root, + drw.drawable = xlib::XCreatePixmap( + drw.dpy, + drw.root, w, h, - xlib::XDefaultDepth((*drw).dpy, (*drw).screen) as c_uint, + xlib::XDefaultDepth(drw.dpy, drw.screen) as c_uint, ); } } - -#[cfg(test)] -mod tests { - #[test] - fn test_utf8decode() { - let tests = [ - (c"GNU Emacs at omsf", 71, 1), - (c"NU Emacs at omsf", 78, 1), - (c"U Emacs at omsf", 85, 1), - (c"๐Ÿ•”", 0x1f554, 4), - ]; - - for (inp, want_u, ret) in tests { - let mut u = 0; - let got = super::utf8decode(inp.as_ptr(), &mut u, super::UTF_SIZ); - assert_eq!(got, ret); - assert_eq!(u, want_u); - } - } -} diff --git a/src/handlers.rs b/src/handlers.rs index 4baef74..1acdf6b 100644 --- a/src/handlers.rs +++ b/src/handlers.rs @@ -58,7 +58,7 @@ pub(crate) fn buttonpress(state: &mut State, e: *mut XEvent) { let mut x = 0; // emulating do-while loop { - x += textw(&mut state.drw, CONFIG.tags[i].as_ptr()); + x += textw(&mut state.drw, &CONFIG.tags[i]); // condition if ev.x < x { break; @@ -72,15 +72,12 @@ pub(crate) fn buttonpress(state: &mut State, e: *mut XEvent) { click = Clk::TagBar; arg = Arg::Ui(1 << i); } else if ev.x - < x + textw( - &mut state.drw, - &raw const (*state.selmon).ltsymbol as *const _, - ) + < x + textw(&mut state.drw, &(*state.selmon).ltsymbol) { click = Clk::LtSymbol; } else if ev.x > (*state.selmon).ww - - textw(&mut state.drw, &raw const state.stext as *const _) + - textw(&mut state.drw, &state.stext) - getsystraywidth() as i32 { click = Clk::StatusText; diff --git a/src/key_handlers.rs b/src/key_handlers.rs index bc80d62..8d7aa8a 100644 --- a/src/key_handlers.rs +++ b/src/key_handlers.rs @@ -258,11 +258,10 @@ pub(crate) fn setlayout(state: &mut State, arg: *const Arg) { [(*state.selmon).pertag.curtag as usize] [(*state.selmon).sellt as usize]; } - libc::strncpy( - (*state.selmon).ltsymbol.as_mut_ptr(), - (*(*state.selmon).lt[(*state.selmon).sellt as usize]).symbol, - size_of_val(&(*state.selmon).ltsymbol), - ); + (*state.selmon).ltsymbol = (*(*state.selmon).lt + [(*state.selmon).sellt as usize]) + .symbol + .clone(); if !(*state.selmon).sel.is_null() { arrange(state, state.selmon); } else { diff --git a/src/layouts.rs b/src/layouts.rs index 813be79..d5b7c27 100644 --- a/src/layouts.rs +++ b/src/layouts.rs @@ -16,12 +16,7 @@ pub(crate) fn monocle(state: &mut State, m: *mut Monitor) { }); if n > 0 { // override layout symbol - libc::snprintf( - (*m).ltsymbol.as_mut_ptr(), - size_of_val(&(*m).ltsymbol), - c"[%d]".as_ptr(), - n, - ); + (*m).ltsymbol = format!("[{n}]"); } cfor!((c = nexttiled((*m).clients); !c.is_null(); c = nexttiled((*c).next)) { resize(state, c, (*m).wx, (*m).wy, (*m).ww - 2 * (*c).bw, (*m).wh - 2 * (*c).bw, 0); diff --git a/src/lib.rs b/src/lib.rs index 6a3153b..5cf045f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -76,11 +76,11 @@ pub struct Cursor { } #[repr(C)] -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Clone)] pub struct Rule { pub class: *const c_char, pub instance: *const c_char, - pub title: *const c_char, + pub title: String, pub tags: c_uint, pub isfloating: bool, pub isterminal: bool, @@ -94,9 +94,9 @@ pub struct Systray { } #[repr(C)] -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Clone)] pub struct Layout { - pub symbol: *const c_char, + pub symbol: String, pub arrange: Option, } @@ -121,7 +121,7 @@ pub struct Pertag { #[repr(C)] #[derive(Debug, Clone)] pub struct Monitor { - pub ltsymbol: [c_char; 16usize], + pub ltsymbol: String, pub mfact: f32, pub nmaster: c_int, pub num: c_int, @@ -149,9 +149,9 @@ pub struct Monitor { } #[repr(C)] -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Clone)] pub struct Client { - pub name: [c_char; 256usize], + pub name: String, pub mina: f32, pub maxa: f32, pub x: c_int, diff --git a/src/main.rs b/src/main.rs index 32c6b2c..0406a4f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,9 +1,8 @@ //! tiling window manager based on dwm use std::cmp::max; -use std::ffi::{c_char, c_int, c_uint, c_ulong, CStr}; +use std::ffi::{c_int, c_uint, c_ulong, CStr}; use std::io::Read; -use std::mem::size_of_val; use std::mem::{size_of, MaybeUninit}; use std::ptr::null_mut; use std::sync::LazyLock; @@ -172,11 +171,7 @@ fn createmon() -> *mut Monitor { (*m).topbar = CONFIG.topbar; (*m).lt[0] = &CONFIG.layouts[0]; (*m).lt[1] = &CONFIG.layouts[1 % CONFIG.layouts.len()]; - libc::strncpy( - &mut (*m).ltsymbol as *mut _, - CONFIG.layouts[0].symbol, - size_of_val(&(*m).ltsymbol), - ); + (*m).ltsymbol = CONFIG.layouts[0].symbol.clone(); (*m).pertag = Pertag { curtag: 1, @@ -269,7 +264,7 @@ fn setup(dpy: *mut Display) -> State { drw, selmon: null_mut(), mons: null_mut(), - stext: ['\0' as c_char; 256], + stext: String::new(), scheme: Default::default(), }; @@ -630,11 +625,7 @@ fn arrange(state: &mut State, mut m: *mut Monitor) { fn arrangemon(state: &mut State, m: *mut Monitor) { log::trace!("arrangemon"); unsafe { - libc::strncpy( - (*m).ltsymbol.as_mut_ptr(), - (*(*m).lt[(*m).sellt as usize]).symbol, - size_of_val(&(*m).ltsymbol), - ); + (*m).ltsymbol = (*(*m).lt[(*m).sellt as usize]).symbol.clone(); let arrange = (*(*m).lt[(*m).sellt as usize]).arrange; if let Some(arrange) = arrange { (arrange)(state, m); @@ -1131,15 +1122,8 @@ fn unfocus(state: &mut State, c: *mut Client, setfocus: bool) { fn updatestatus(state: &mut State) { log::trace!("updatestatus"); unsafe { - if gettextprop( - state.dpy, - ROOT, - XA_WM_NAME, - state.stext.as_mut_ptr(), - size_of_val(&state.stext) as u32, - ) == 0 - { - libc::strcpy(&raw mut state.stext as *mut _, c"rwm-1.0".as_ptr()); + if gettextprop(state.dpy, ROOT, XA_WM_NAME, &mut state.stext) == 0 { + state.stext = "rwm-1.0".to_string(); } drawbar(state, state.selmon); updatesystray(state); @@ -1251,8 +1235,7 @@ fn updatesystray(state: &mut State) { let mut i: *mut Client; let m: *mut Monitor = systraytomon(state, null_mut()); let mut x: c_int = (*m).mx + (*m).mw; - let sw = textw(&mut state.drw, &raw const state.stext as *const _) - - LRPAD + let sw = textw(&mut state.drw, &state.stext) - LRPAD + CONFIG.systrayspacing as i32; let mut w = 1; @@ -1431,7 +1414,7 @@ fn systraytomon(state: &State, m: *mut Monitor) -> *mut Monitor { } } -fn textw(drw: &mut Drw, x: *const c_char) -> c_int { +fn textw(drw: &mut Drw, x: &str) -> c_int { log::trace!("textw"); unsafe { drw::fontset_getwidth(drw, x) as c_int + LRPAD } } @@ -1460,9 +1443,7 @@ fn drawbar(state: &mut State, m: *mut Monitor) { if m == state.selmon { // status is only drawn on selected monitor drw::setscheme(&mut state.drw, state.scheme[Scheme::Norm].clone()); - tw = textw(&mut state.drw, &raw const state.stext as *const _) - - LRPAD / 2 - + 2; // 2px right padding + tw = textw(&mut state.drw, &state.stext) - LRPAD / 2 + 2; // 2px right padding log::trace!("drawbar: text"); drw::text( &mut state.drw, @@ -1471,7 +1452,7 @@ fn drawbar(state: &mut State, m: *mut Monitor) { tw as u32, state.bh as u32, (LRPAD / 2 - 2) as u32, - &raw const state.stext as *const _, + &state.stext, 0, ); } @@ -1490,7 +1471,7 @@ fn drawbar(state: &mut State, m: *mut Monitor) { let mut x = 0; for (i, tag) in CONFIG.tags.iter().enumerate() { let text = tag.to_owned(); - let w = textw(&mut state.drw, text.as_ptr()); + let w = textw(&mut state.drw, &text); drw::setscheme( &mut state.drw, state.scheme[if ((*m).tagset[(*m).seltags as usize] & 1 << i) @@ -1510,7 +1491,7 @@ fn drawbar(state: &mut State, m: *mut Monitor) { w as u32, state.bh as u32, LRPAD as u32 / 2, - text.as_ptr(), + &text, (urg as i32) & 1 << i, ); @@ -1525,13 +1506,13 @@ fn drawbar(state: &mut State, m: *mut Monitor) { && !(*state.selmon).sel.is_null() && ((*(*state.selmon).sel).tags & 1 << i) != 0) as c_int, - (urg & 1 << i) as c_int, + (urg & 1 << i) != 0, ); } x += w as i32; } - let w = textw(&mut state.drw, (*m).ltsymbol.as_ptr()); + let w = textw(&mut state.drw, &(*m).ltsymbol); drw::setscheme(&mut state.drw, state.scheme[Scheme::Norm].clone()); log::trace!("drawbar: text 3"); x = drw::text( @@ -1541,7 +1522,7 @@ fn drawbar(state: &mut State, m: *mut Monitor) { w as u32, state.bh as u32, LRPAD as u32 / 2, - (*m).ltsymbol.as_ptr(), + &(*m).ltsymbol, 0, ) as i32; log::trace!("finished drawbar text 3"); @@ -1566,7 +1547,7 @@ fn drawbar(state: &mut State, m: *mut Monitor) { w as u32, state.bh as u32, LRPAD as u32 / 2, - (*(*m).sel).name.as_ptr(), + &(*(*m).sel).name, 0, ); if (*(*m).sel).isfloating { @@ -1577,7 +1558,7 @@ fn drawbar(state: &mut State, m: *mut Monitor) { boxw, boxw, (*(*m).sel).isfixed, - 0, + false, ); } } else { @@ -1592,12 +1573,12 @@ fn drawbar(state: &mut State, m: *mut Monitor) { w as u32, state.bh as u32, 1, - 1, + true, ); } } drw::map( - &mut state.drw, + &state.drw, (*m).barwin, 0, 0, @@ -1611,15 +1592,10 @@ fn gettextprop( dpy: *mut Display, w: Window, atom: Atom, - text: *mut i8, - size: u32, + text: &mut String, ) -> c_int { log::trace!("gettextprop"); unsafe { - if text.is_null() || size == 0 { - return 0; - } - *text = '\0' as i8; let mut name = xlib::XTextProperty { value: std::ptr::null_mut(), encoding: 0, @@ -1634,7 +1610,8 @@ fn gettextprop( let mut n = 0; let mut list: *mut *mut i8 = std::ptr::null_mut(); if name.encoding == XA_STRING { - libc::strncpy(text, name.value as *mut _, size as usize - 1); + let name_val = CStr::from_ptr(name.value.cast()); + *text = name_val.to_string_lossy().to_string(); } else if xlib::XmbTextPropertyToTextList( dpy, &name, @@ -1644,11 +1621,28 @@ fn gettextprop( && n > 0 && !(*list).is_null() { - libc::strncpy(text, *list, size as usize - 1); + // TODO handle this properly. *list is a "string" in some encoding I + // don't understand. the main test case I noticed an issue with was + // a browser tab with a ยท, which was initially taking the value -73 + // as an i8, which is the correct character 183 as a u8. This + // solution works for characters like that that fit in a u8 but + // doesn't work for larger characters like ัž (cyrillic short u). + // actually `list` doesn't even contain the right characters for the + // short u. it just starts at the space after it, as demonstrated by + // using libc::printf to try to print it. + // + // Looks like my encoding is different. Getting 238 in Rust vs 287 + // in C. Using XGetAtomName shows 238 is UTF8_STRING, while 287 is + // _NET_WM_WINDOW_TYPE_POPUP_MENU (??). In dwm in the VM, 287 is + // also UTF8_STRING + *text = String::new(); + let mut c = *list; + while *c != 0 { + text.push(char::from(*c as u8)); + c = c.offset(1); + } xlib::XFreeStringList(list); } - let p = text.offset(size as isize - 1); - *p = '\0' as i8; xlib::XFree(name.value as *mut _); } 1 @@ -2097,7 +2091,7 @@ fn cleanup(mut state: State) { let a = Arg::Ui(!0); view(&mut state, &a); (*state.selmon).lt[(*state.selmon).sellt as usize] = - &Layout { symbol: c"".as_ptr(), arrange: None }; + &Layout { symbol: String::new(), arrange: None }; let mut m = state.mons; while !m.is_null() { @@ -2509,6 +2503,7 @@ fn manage(state: &mut State, w: Window, wa: *mut xlib::XWindowAttributes) { (*c).h = wa.height; (*c).oldh = wa.height; (*c).oldbw = wa.border_width; + (*c).name = String::new(); let mut term: *mut Client = null_mut(); @@ -2551,8 +2546,7 @@ fn manage(state: &mut State, w: Window, wa: *mut xlib::XWindowAttributes) { // like something meant to be handled by RULES (*state.selmon).tagset[(*state.selmon).seltags as usize] &= !*SCRATCHTAG; - if libc::strcmp((*c).name.as_ptr(), CONFIG.scratchpadname.as_ptr()) == 0 - { + if (*c).name == CONFIG.scratchpadname { (*c).tags = *SCRATCHTAG; (*(*c).mon).tagset[(*(*c).mon).seltags as usize] |= (*c).tags; (*c).isfloating = true; @@ -2813,8 +2807,7 @@ fn applyrules(state: &mut State, c: *mut Client) { }; for r in &CONFIG.rules { - if (r.title.is_null() - || !libc::strstr((*c).name.as_ptr(), r.title).is_null()) + if (r.title.is_empty() || (*c).name.contains(&r.title)) && (r.class.is_null() || !libc::strstr(class.as_ptr(), r.class).is_null()) && (r.instance.is_null() @@ -2909,24 +2902,14 @@ fn updatetitle(state: &mut State, c: *mut Client) { state.dpy, (*c).win, state.netatom[Net::WMName as usize], - &mut (*c).name as *mut _, - size_of_val(&(*c).name) as u32, + &mut (*c).name, ) == 0 { - gettextprop( - state.dpy, - (*c).win, - XA_WM_NAME, - &mut (*c).name as *mut _, - size_of_val(&(*c).name) as u32, - ); + gettextprop(state.dpy, (*c).win, XA_WM_NAME, &mut (*c).name); } - if (*c).name[0] == '\0' as i8 { + if (*c).name.is_empty() { /* hack to mark broken clients */ - libc::strcpy( - &mut (*c).name as *mut _, - BROKEN.as_ptr() as *const c_char, - ); + (*c).name = BROKEN.to_string_lossy().to_string(); } } } diff --git a/src/state.rs b/src/state.rs index 62b322f..69c5964 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,7 +1,4 @@ -use std::{ - ffi::{c_char, c_int}, - ops::Index, -}; +use std::{ffi::c_int, ops::Index}; use x11::xlib::{self, Atom, Display}; @@ -50,7 +47,7 @@ pub struct State { pub cursors: Cursors, pub selmon: *mut Monitor, pub mons: *mut Monitor, - pub stext: [c_char; 256], + pub stext: String, pub scheme: ClrScheme, } diff --git a/src/tests.rs b/src/tests.rs index 356331f..cabee3b 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -43,7 +43,7 @@ fn main() { CONFIG .tags .iter() - .map(|tag| textw(&mut state.drw, tag.as_ptr())) + .map(|tag| textw(&mut state.drw, tag)) .sum::() + 5, state.dpy, diff --git a/src/util.rs b/src/util.rs index d491f84..d94d65d 100644 --- a/src/util.rs +++ b/src/util.rs @@ -14,8 +14,3 @@ pub fn ecalloc(nmemb: size_t, size: size_t) -> *mut c_void { } ret } - -#[inline] -pub(crate) fn between(x: T, a: T, b: T) -> bool { - a <= x && x <= b -}