From 38f479588668ca364473ddfb3aa252f77eca50a3 Mon Sep 17 00:00:00 2001 From: Ulyssa Date: Thu, 5 Jan 2023 18:12:25 -0800 Subject: [PATCH] Support displaying and editing room descriptions (#12) --- Cargo.lock | 4 +- Cargo.toml | 2 +- README.md | 4 +- src/base.rs | 19 ++++++++ src/commands.rs | 69 ++++++++++++++++++++++++++++ src/config.rs | 33 +++++++++++--- src/main.rs | 7 ++- src/tests.rs | 10 +++- src/windows/mod.rs | 96 +++++++++++++++++++++++++++------------ src/windows/room/chat.rs | 7 +++ src/windows/room/mod.rs | 39 ++++++++++++++-- src/windows/room/space.rs | 8 +++- src/worker.rs | 43 ++++++++++++++++-- 13 files changed, 286 insertions(+), 55 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5d998a8..0f0a333 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1600,9 +1600,9 @@ dependencies = [ [[package]] name = "modalkit" -version = "0.0.8" +version = "0.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f4e400066e546471efee517b7e5e3ca5af2c04014e76289aecc7af621011bba" +checksum = "28a676fc7ab6a9fd329ff82d9d291370aafcf904ac3ff9f72397f64529cb1b2d" dependencies = [ "anymap2", "bitflags", diff --git a/Cargo.toml b/Cargo.toml index adecef1..3ab8084 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,7 @@ dirs = "4.0.0" futures = "0.3.21" gethostname = "0.4.1" matrix-sdk = {version = "0.6", default-features = false, features = ["e2e-encryption", "sled", "rustls-tls"]} -modalkit = "0.0.8" +modalkit = "0.0.9" regex = "^1.5" rpassword = "^7.2" serde = "^1.0" diff --git a/README.md b/README.md index 2849335..b4a7c21 100644 --- a/README.md +++ b/README.md @@ -43,8 +43,8 @@ two other TUI clients and Element Web: | Room tag editing | :x: ([#15]) | :heavy_check_mark: | :x: | :heavy_check_mark: | | Search joined rooms | :x: ([#16]) | :heavy_check_mark: | :x: | :heavy_check_mark: | | Room user list | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | -| Display Room Description | :x: ([#12]) | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | -| Edit Room Description | :x: ([#12]) | :x: | :heavy_check_mark: | :heavy_check_mark: | +| Display Room Description | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | +| Edit Room Description | :heavy_check_mark: | :x: | :heavy_check_mark: | :heavy_check_mark: | | Highlights | :x: ([#8]) | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | | Pushrules | :x: | :heavy_check_mark: | :x: | :heavy_check_mark: | | Send read markers | :x: ([#11]) | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | diff --git a/src/base.rs b/src/base.rs index 58f0087..52b8aec 100644 --- a/src/base.rs +++ b/src/base.rs @@ -59,9 +59,22 @@ pub enum VerifyAction { Mismatch, } +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum SetRoomField { + Name(String), + Topic(String), +} + #[derive(Clone, Debug, Eq, PartialEq)] pub enum RoomAction { Members(Box>), + Set(SetRoomField), +} + +impl From for RoomAction { + fn from(act: SetRoomField) -> Self { + RoomAction::Set(act) + } } #[derive(Clone, Debug, Eq, PartialEq)] @@ -73,6 +86,12 @@ pub enum IambAction { ToggleScrollbackFocus, } +impl From for IambAction { + fn from(act: RoomAction) -> Self { + IambAction::Room(act) + } +} + impl ApplicationAction for IambAction { fn is_edit_sequence(&self, _: &C) -> SequenceStatus { match self { diff --git a/src/commands.rs b/src/commands.rs index 51c4567..8ce02f1 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -12,6 +12,7 @@ use crate::base::{ ProgramCommands, ProgramContext, RoomAction, + SetRoomField, VerifyAction, }; @@ -125,11 +126,35 @@ fn iamb_join(desc: CommandDescription, ctx: &mut ProgContext) -> ProgResult { return Ok(step); } +fn iamb_set(desc: CommandDescription, ctx: &mut ProgContext) -> ProgResult { + let mut args = desc.arg.strings()?; + + if args.len() != 2 { + return Result::Err(CommandError::InvalidArgument); + } + + let field = args.remove(0); + let value = args.remove(0); + + let act: IambAction = match field.as_str() { + "room.name" => RoomAction::Set(SetRoomField::Name(value)).into(), + "room.topic" => RoomAction::Set(SetRoomField::Topic(value)).into(), + _ => { + return Result::Err(CommandError::InvalidArgument); + }, + }; + + let step = CommandStep::Continue(act.into(), ctx.context.take()); + + return Ok(step); +} + fn add_iamb_commands(cmds: &mut ProgramCommands) { cmds.add_command(ProgramCommand { names: vec!["dms".into()], f: iamb_dms }); cmds.add_command(ProgramCommand { names: vec!["join".into()], f: iamb_join }); cmds.add_command(ProgramCommand { names: vec!["members".into()], f: iamb_members }); cmds.add_command(ProgramCommand { names: vec!["rooms".into()], f: iamb_rooms }); + cmds.add_command(ProgramCommand { names: vec!["set".into()], f: iamb_set }); cmds.add_command(ProgramCommand { names: vec!["spaces".into()], f: iamb_spaces }); cmds.add_command(ProgramCommand { names: vec!["verify".into()], f: iamb_verify }); cmds.add_command(ProgramCommand { names: vec!["welcome".into()], f: iamb_welcome }); @@ -214,4 +239,48 @@ mod tests { let res = cmds.input_cmd("join foo bar", ctx.clone()); assert_eq!(res, Err(CommandError::InvalidArgument)); } + + #[test] + fn test_cmd_set() { + let mut cmds = setup_commands(); + let ctx = ProgramContext::default(); + + let res = cmds + .input_cmd("set room.topic \"Lots of fun discussion!\"", ctx.clone()) + .unwrap(); + let act = IambAction::Room(SetRoomField::Topic("Lots of fun discussion!".into()).into()); + assert_eq!(res, vec![(act.into(), ctx.clone())]); + + let res = cmds + .input_cmd("set room.topic The\\ Discussion\\ Room", ctx.clone()) + .unwrap(); + let act = IambAction::Room(SetRoomField::Topic("The Discussion Room".into()).into()); + assert_eq!(res, vec![(act.into(), ctx.clone())]); + + let res = cmds.input_cmd("set room.topic Development", ctx.clone()).unwrap(); + let act = IambAction::Room(SetRoomField::Topic("Development".into()).into()); + assert_eq!(res, vec![(act.into(), ctx.clone())]); + + let res = cmds.input_cmd("set room.name Development", ctx.clone()).unwrap(); + let act = IambAction::Room(SetRoomField::Name("Development".into()).into()); + assert_eq!(res, vec![(act.into(), ctx.clone())]); + + let res = cmds + .input_cmd("set room.name \"Application Development\"", ctx.clone()) + .unwrap(); + let act = IambAction::Room(SetRoomField::Name("Application Development".into()).into()); + assert_eq!(res, vec![(act.into(), ctx.clone())]); + + let res = cmds.input_cmd("set", ctx.clone()); + assert_eq!(res, Err(CommandError::InvalidArgument)); + + let res = cmds.input_cmd("set room.name", ctx.clone()); + assert_eq!(res, Err(CommandError::InvalidArgument)); + + let res = cmds.input_cmd("set room.topic", ctx.clone()); + assert_eq!(res, Err(CommandError::InvalidArgument)); + + let res = cmds.input_cmd("set room.topic A B C", ctx.clone()); + assert_eq!(res, Err(CommandError::InvalidArgument)); + } } diff --git a/src/config.rs b/src/config.rs index bef88a5..6a6aa89 100644 --- a/src/config.rs +++ b/src/config.rs @@ -100,25 +100,44 @@ impl Tunables { #[derive(Clone)] pub struct DirectoryValues { pub cache: PathBuf, + pub logs: PathBuf, + pub downloads: PathBuf, } #[derive(Clone, Default, Deserialize)] pub struct Directories { pub cache: Option, + pub logs: Option, + pub downloads: Option, } impl Directories { fn merge(self, other: Self) -> Self { - Directories { cache: self.cache.or(other.cache) } + Directories { + cache: self.cache.or(other.cache), + logs: self.logs.or(other.logs), + downloads: self.downloads.or(other.downloads), + } } fn values(self) -> DirectoryValues { - DirectoryValues { - cache: self - .cache - .or_else(dirs::cache_dir) - .expect("no dirs.cache value configured!"), - } + let cache = self + .cache + .or_else(dirs::cache_dir) + .expect("no dirs.cache value configured!"); + + let logs = self.logs.unwrap_or_else(|| { + let mut dir = cache.clone(); + dir.push("logs"); + dir + }); + + let downloads = self + .downloads + .or_else(dirs::download_dir) + .expect("no dirs.download value configured!"); + + DirectoryValues { cache, logs, downloads } } } diff --git a/src/main.rs b/src/main.rs index 30d9740..da27efb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -162,7 +162,7 @@ impl Application { term.draw(|f| { let area = f.size(); - let screen = Screen::new(store).showmode(modestr); + let screen = Screen::new(store).showmode(modestr).borders(true); f.render_stateful_widget(screen, area, sstate); if let Some((cx, cy)) = sstate.get_term_cursor() { @@ -457,11 +457,10 @@ async fn main() -> IambResult<()> { // Set up the tracing subscriber so we can log client messages. let log_prefix = format!("iamb-log-{}", settings.profile_name); - let mut log_dir = settings.dirs.cache.clone(); - log_dir.push("logs"); + let log_dir = settings.dirs.logs.as_path(); create_dir_all(settings.matrix_dir.as_path())?; - create_dir_all(log_dir.as_path())?; + create_dir_all(log_dir)?; let appender = tracing_appender::rolling::daily(log_dir, log_prefix); let (appender, _) = tracing_appender::non_blocking(appender); diff --git a/src/tests.rs b/src/tests.rs index 5a63e1e..79e80bf 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -110,6 +110,14 @@ pub fn mock_room() -> RoomInfo { } } +pub fn mock_dirs() -> DirectoryValues { + DirectoryValues { + cache: PathBuf::new(), + logs: PathBuf::new(), + downloads: PathBuf::new(), + } +} + pub fn mock_settings() -> ApplicationSettings { ApplicationSettings { matrix_dir: PathBuf::new(), @@ -122,7 +130,7 @@ pub fn mock_settings() -> ApplicationSettings { dirs: None, }, tunables: TunableValues { typing_notice: true, typing_notice_display: true }, - dirs: DirectoryValues { cache: PathBuf::new() }, + dirs: mock_dirs(), } } diff --git a/src/windows/mod.rs b/src/windows/mod.rs index db41e1e..133a515 100644 --- a/src/windows/mod.rs +++ b/src/windows/mod.rs @@ -13,7 +13,7 @@ use modalkit::tui::{ layout::{Alignment, Rect}, style::{Modifier as StyleModifier, Style}, text::{Span, Spans, Text}, - widgets::{Block, Borders, StatefulWidget, Widget}, + widgets::StatefulWidget, }; use modalkit::{ @@ -71,6 +71,21 @@ use self::{room::RoomState, welcome::WelcomeState}; pub mod room; pub mod welcome; +#[inline] +fn bold_style() -> Style { + Style::default().add_modifier(StyleModifier::BOLD) +} + +#[inline] +fn bold_span(s: &str) -> Span { + Span::styled(s, bold_style()) +} + +#[inline] +fn bold_spans(s: &str) -> Spans { + bold_span(s).into() +} + #[inline] fn selected_style(selected: bool) -> Style { if selected { @@ -168,22 +183,6 @@ impl IambWindow { return Err(err); } } - - pub fn get_title(&self, store: &mut ProgramStore) -> String { - match self { - IambWindow::DirectList(_) => "Direct Messages".to_string(), - IambWindow::RoomList(_) => "Rooms".to_string(), - IambWindow::SpaceList(_) => "Spaces".to_string(), - IambWindow::VerifyList(_) => "Verifications".to_string(), - IambWindow::Welcome(_) => "Welcome to iamb".to_string(), - - IambWindow::Room(w) => w.get_title(store), - IambWindow::MemberList(_, room_id) => { - let title = store.application.get_room_title(room_id.as_ref()); - format!("Room Members: {}", title) - }, - } - } } pub type DirectListState = ListState; @@ -281,13 +280,8 @@ impl TerminalCursor for IambWindow { impl WindowOps for IambWindow { fn draw(&mut self, area: Rect, buf: &mut Buffer, focused: bool, store: &mut ProgramStore) { - let title = self.get_title(store); - let block = Block::default().title(title.as_str()).borders(Borders::ALL); - let inner = block.inner(area); - block.render(area, buf); - match self { - IambWindow::Room(state) => state.draw(inner, buf, focused, store), + IambWindow::Room(state) => state.draw(area, buf, focused, store), IambWindow::DirectList(state) => { let dms = store.application.worker.direct_messages(); let items = dms.into_iter().map(|(id, name)| DirectItem::new(id, name, store)); @@ -297,7 +291,7 @@ impl WindowOps for IambWindow { .empty_message("No direct messages yet!") .empty_alignment(Alignment::Center) .focus(focused) - .render(inner, buf, state); + .render(area, buf, state); }, IambWindow::MemberList(state, room_id) => { if let Ok(mems) = store.application.worker.members(room_id.clone()) { @@ -309,7 +303,7 @@ impl WindowOps for IambWindow { .empty_message("No users here yet!") .empty_alignment(Alignment::Center) .focus(focused) - .render(inner, buf, state); + .render(area, buf, state); }, IambWindow::RoomList(state) => { let joined = store.application.worker.joined_rooms(); @@ -320,20 +314,20 @@ impl WindowOps for IambWindow { .empty_message("You haven't joined any rooms yet") .empty_alignment(Alignment::Center) .focus(focused) - .render(inner, buf, state); + .render(area, buf, state); }, IambWindow::SpaceList(state) => { let spaces = store.application.worker.spaces(); let items = spaces.into_iter().map(|(room, name)| SpaceItem::new(room, name, store)); state.set(items.collect()); - state.draw(inner, buf, focused, store); + state.draw(area, buf, focused, store); List::new(store) .empty_message("You haven't joined any spaces yet") .empty_alignment(Alignment::Center) .focus(focused) - .render(inner, buf, state); + .render(area, buf, state); }, IambWindow::VerifyList(state) => { let verifications = &store.application.verifications; @@ -348,9 +342,9 @@ impl WindowOps for IambWindow { .empty_message("No in-progress verifications") .empty_alignment(Alignment::Center) .focus(focused) - .render(inner, buf, state); + .render(area, buf, state); }, - IambWindow::Welcome(state) => state.draw(inner, buf, focused, store), + IambWindow::Welcome(state) => state.draw(area, buf, focused, store), } } @@ -394,6 +388,44 @@ impl Window for IambWindow { } } + fn get_tab_title(&self, store: &mut ProgramStore) -> Spans { + match self { + IambWindow::DirectList(_) => bold_spans("Direct Messages"), + IambWindow::RoomList(_) => bold_spans("Rooms"), + IambWindow::SpaceList(_) => bold_spans("Spaces"), + IambWindow::VerifyList(_) => bold_spans("Verifications"), + IambWindow::Welcome(_) => bold_spans("Welcome to iamb"), + + IambWindow::Room(w) => { + let title = store.application.get_room_title(w.id()); + + Spans::from(title) + }, + IambWindow::MemberList(_, room_id) => { + let title = store.application.get_room_title(room_id.as_ref()); + + Spans(vec![bold_span("Room Members: "), title.into()]) + }, + } + } + + fn get_win_title(&self, store: &mut ProgramStore) -> Spans { + match self { + IambWindow::DirectList(_) => bold_spans("Direct Messages"), + IambWindow::RoomList(_) => bold_spans("Rooms"), + IambWindow::SpaceList(_) => bold_spans("Spaces"), + IambWindow::VerifyList(_) => bold_spans("Verifications"), + IambWindow::Welcome(_) => bold_spans("Welcome to iamb"), + + IambWindow::Room(w) => w.get_title(store), + IambWindow::MemberList(_, room_id) => { + let title = store.application.get_room_title(room_id.as_ref()); + + Spans(vec![bold_span("Room Members: "), title.into()]) + }, + } + } + fn open(id: IambId, store: &mut ProgramStore) -> IambResult { match id { IambId::Room(room_id) => { @@ -464,6 +496,10 @@ impl Window for IambWindow { Err(err) } + + fn unnamed(store: &mut ProgramStore) -> IambResult { + Self::open(IambId::RoomList, store) + } } #[derive(Clone)] diff --git a/src/windows/room/chat.rs b/src/windows/room/chat.rs index 3ce1e3e..9a74c27 100644 --- a/src/windows/room/chat.rs +++ b/src/windows/room/chat.rs @@ -44,6 +44,7 @@ use super::scrollback::{Scrollback, ScrollbackState}; pub struct ChatState { room_id: OwnedRoomId, + room: MatrixRoom, tbox: TextBoxState, sent: HistoryList, @@ -63,6 +64,7 @@ impl ChatState { ChatState { room_id, + room, tbox, sent: HistoryList::new(EditRope::from(""), 100), @@ -80,6 +82,10 @@ impl ChatState { }; } + pub fn room(&self) -> &MatrixRoom { + &self.room + } + pub fn id(&self) -> &RoomId { &self.room_id } @@ -133,6 +139,7 @@ impl WindowOps for ChatState { ChatState { room_id: self.room_id.clone(), + room: self.room.clone(), tbox, sent: self.sent.clone(), diff --git a/src/windows/room/mod.rs b/src/windows/room/mod.rs index e1d8624..b8577fe 100644 --- a/src/windows/room/mod.rs +++ b/src/windows/room/mod.rs @@ -2,7 +2,13 @@ use matrix_sdk::room::Room as MatrixRoom; use matrix_sdk::ruma::RoomId; use matrix_sdk::DisplayName; -use modalkit::tui::{buffer::Buffer, layout::Rect, widgets::StatefulWidget}; +use modalkit::tui::{ + buffer::Buffer, + layout::Rect, + style::{Modifier as StyleModifier, Style}, + text::{Span, Spans}, + widgets::StatefulWidget, +}; use modalkit::{ editing::action::{ @@ -90,7 +96,7 @@ impl RoomState { &mut self, act: RoomAction, _: ProgramContext, - _: &mut ProgramStore, + store: &mut ProgramStore, ) -> IambResult, ProgramContext)>> { match act { RoomAction::Members(mut cmd) => { @@ -103,11 +109,29 @@ impl RoomState { Ok(vec![(act, cmd.context.take())]) }, + RoomAction::Set(field) => { + store.application.worker.set_room(self.id().to_owned(), field)?; + + Ok(vec![]) + }, } } - pub fn get_title(&self, store: &mut ProgramStore) -> String { - store.application.get_room_title(self.id()) + pub fn get_title(&self, store: &mut ProgramStore) -> Spans { + let title = store.application.get_room_title(self.id()); + let style = Style::default().add_modifier(StyleModifier::BOLD); + let mut spans = vec![Span::styled(title, style)]; + + match self.room().topic() { + Some(desc) if !desc.is_empty() => { + spans.push(" (".into()); + spans.push(desc.into()); + spans.push(")".into()); + }, + _ => {}, + } + + Spans(spans) } pub fn focus_toggle(&mut self) { @@ -117,6 +141,13 @@ impl RoomState { } } + pub fn room(&self) -> &MatrixRoom { + match self { + RoomState::Chat(chat) => chat.room(), + RoomState::Space(space) => space.room(), + } + } + pub fn id(&self) -> &RoomId { match self { RoomState::Chat(chat) => chat.id(), diff --git a/src/windows/room/space.rs b/src/windows/room/space.rs index 9a562aa..f03f64a 100644 --- a/src/windows/room/space.rs +++ b/src/windows/room/space.rs @@ -18,6 +18,7 @@ use crate::windows::RoomItem; pub struct SpaceState { room_id: OwnedRoomId, + room: MatrixRoom, list: ListState, } @@ -27,7 +28,11 @@ impl SpaceState { let content = IambBufferId::Room(room_id.clone(), RoomFocus::Scrollback); let list = ListState::new(content, vec![]); - SpaceState { room_id, list } + SpaceState { room_id, room, list } + } + + pub fn room(&self) -> &MatrixRoom { + &self.room } pub fn id(&self) -> &RoomId { @@ -37,6 +42,7 @@ impl SpaceState { pub fn dup(&self, store: &mut ProgramStore) -> Self { SpaceState { room_id: self.room_id.clone(), + room: self.room.clone(), list: self.list.dup(store), } } diff --git a/src/worker.rs b/src/worker.rs index 2b88ece..2e00db5 100644 --- a/src/worker.rs +++ b/src/worker.rs @@ -30,8 +30,11 @@ use matrix_sdk::{ start::{OriginalSyncKeyVerificationStartEvent, ToDeviceKeyVerificationStartEvent}, VerificationMethod, }, - room::message::{MessageType, RoomMessageEventContent, TextMessageEventContent}, - room::name::RoomNameEventContent, + room::{ + message::{MessageType, RoomMessageEventContent, TextMessageEventContent}, + name::RoomNameEventContent, + topic::RoomTopicEventContent, + }, typing::SyncTypingEvent, AnyMessageLikeEvent, AnyTimelineEvent, @@ -51,7 +54,7 @@ use matrix_sdk::{ use modalkit::editing::action::{EditInfo, InfoMessage, UIError}; use crate::{ - base::{AsyncProgramStore, IambError, IambResult, VerifyAction}, + base::{AsyncProgramStore, IambError, IambResult, SetRoomField, VerifyAction}, message::{Message, MessageFetchResult, MessageTimeStamp}, ApplicationSettings, }; @@ -106,6 +109,7 @@ pub enum WorkerTask { SpaceMembers(OwnedRoomId, ClientReply>>), Spaces(ClientReply>), SendMessage(OwnedRoomId, String, ClientReply>), + SetRoom(OwnedRoomId, SetRoomField, ClientReply>), TypingNotice(OwnedRoomId), Verify(VerifyAction, SasVerification, ClientReply>), VerifyRequest(OwnedUserId, ClientReply>), @@ -204,6 +208,14 @@ impl Requester { return response.recv(); } + pub fn set_room(&self, room_id: OwnedRoomId, ev: SetRoomField) -> IambResult<()> { + let (reply, response) = oneshot(); + + self.tx.send(WorkerTask::SetRoom(room_id, ev, reply)).unwrap(); + + return response.recv(); + } + pub fn spaces(&self) -> Vec<(MatrixRoom, DisplayName)> { let (reply, response) = oneshot(); @@ -340,6 +352,10 @@ impl ClientWorker { assert!(self.initialized); reply.send(self.members(room_id).await); }, + WorkerTask::SetRoom(room_id, field, reply) => { + assert!(self.initialized); + reply.send(self.set_room(room_id, field).await); + }, WorkerTask::SpaceMembers(space, reply) => { assert!(self.initialized); reply.send(self.space_members(space).await); @@ -765,6 +781,27 @@ impl ClientWorker { } } + async fn set_room(&mut self, room_id: OwnedRoomId, field: SetRoomField) -> IambResult<()> { + let room = if let Some(r) = self.client.get_joined_room(&room_id) { + r + } else { + return Err(IambError::UnknownRoom(room_id).into()); + }; + + match field { + SetRoomField::Name(name) => { + let ev = RoomNameEventContent::new(name.into()); + let _ = room.send_state_event(ev).await.map_err(IambError::from)?; + }, + SetRoomField::Topic(topic) => { + let ev = RoomTopicEventContent::new(topic); + let _ = room.send_state_event(ev).await.map_err(IambError::from)?; + }, + } + + Ok(()) + } + async fn space_members(&mut self, space: OwnedRoomId) -> IambResult> { let mut req = SpaceHierarchyRequest::new(&space); req.limit = Some(1000u32.into());