cli arguments, clippy passes
This commit is contained in:
parent
b4da358591
commit
a100c9be52
8 changed files with 528 additions and 958 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
target
|
||||
result
|
821
Cargo.lock
generated
821
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
21
Cargo.toml
21
Cargo.toml
|
@ -5,4 +5,23 @@ edition = "2024"
|
|||
|
||||
[dependencies]
|
||||
nix = { version = "0.30.1", features = ["term", "process", "fs"], default-features = false }
|
||||
iced = { version = "0.13.1", features = ["advanced", "smol"] }
|
||||
iced = { version = "0.13.1", features = ["advanced", "smol", "wgpu"], default-features = false }
|
||||
bpaf = { version = "0.9.20", features = ["derive"], default-features = false }
|
||||
|
||||
[lints.clippy]
|
||||
cargo = "deny"
|
||||
complexity = "deny"
|
||||
correctness = "deny"
|
||||
nursery = "deny"
|
||||
pedantic = "deny"
|
||||
perf = "deny"
|
||||
restriction = "deny"
|
||||
style = "deny"
|
||||
suspicious = "deny"
|
||||
|
||||
[profile.release]
|
||||
strip = true
|
||||
opt-level = "z"
|
||||
lto = true
|
||||
codegen-units = 1
|
||||
panic = "abort"
|
||||
|
|
6
flake.lock
generated
6
flake.lock
generated
|
@ -22,11 +22,11 @@
|
|||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1751271578,
|
||||
"narHash": "sha256-P/SQmKDu06x8yv7i0s8bvnnuJYkxVGBWLWHaU+tt4YY=",
|
||||
"lastModified": 1751792365,
|
||||
"narHash": "sha256-J1kI6oAj25IG4EdVlg2hQz8NZTBNYvIS0l4wpr9KcUo=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "3016b4b15d13f3089db8a41ef937b13a9e33a8df",
|
||||
"rev": "1fd8bada0b6117e6c7eb54aad5813023eed37ccb",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
11
flake.nix
11
flake.nix
|
@ -13,12 +13,19 @@
|
|||
}: let
|
||||
system = "x86_64-linux";
|
||||
pkgs = nixpkgs.legacyPackages.${system};
|
||||
in {
|
||||
in rec {
|
||||
packages.${system} = {
|
||||
default = pkgs.callPackage ./package.nix {
|
||||
naersk = pkgs.callPackage naersk {};
|
||||
};
|
||||
};
|
||||
# nixosModules.${system}.default = ./module.nix;
|
||||
devShells.${system}.default = pkgs.mkShell {
|
||||
nativeBuildInputs =
|
||||
[
|
||||
pkgs.clippy
|
||||
]
|
||||
++ packages.${system}.default.buildInputs
|
||||
++ packages.${system}.default.nativeBuildInputs;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
makeWrapper,
|
||||
naersk,
|
||||
pkg-config,
|
||||
upx,
|
||||
wayland,
|
||||
xorg,
|
||||
...
|
||||
|
@ -33,8 +34,10 @@ naersk.buildPackage rec {
|
|||
nativeBuildInputs = [
|
||||
pkg-config
|
||||
makeWrapper
|
||||
upx
|
||||
];
|
||||
postInstall = ''
|
||||
upx --lzma $out/bin/${meta.mainProgram}
|
||||
wrapProgram "$out/bin/${meta.mainProgram}" --prefix LD_LIBRARY_PATH : "${lib.makeLibraryPath buildInputs}"
|
||||
'';
|
||||
meta = {
|
||||
|
|
574
src/lib.rs
574
src/lib.rs
|
@ -1,191 +1,455 @@
|
|||
use iced::widget::text_input::Id;
|
||||
use iced::widget::{column, row, scrollable, text, text_input};
|
||||
use iced::{Element, Font, Task, keyboard};
|
||||
//! this crate runs a terminal
|
||||
|
||||
#![expect(
|
||||
clippy::needless_return,
|
||||
clippy::shadow_reuse,
|
||||
clippy::blanket_clippy_restriction_lints,
|
||||
clippy::must_use_candidate,
|
||||
clippy::missing_trait_methods,
|
||||
clippy::pattern_type_mismatch,
|
||||
clippy::std_instead_of_alloc,
|
||||
clippy::cargo_common_metadata,
|
||||
clippy::multiple_crate_versions,
|
||||
clippy::semicolon_outside_block,
|
||||
static_mut_refs,
|
||||
unused_doc_comments,
|
||||
reason = ""
|
||||
)]
|
||||
|
||||
use bpaf::Bpaf;
|
||||
|
||||
use iced::widget::{column, rich_text, row, scrollable, span, text};
|
||||
use iced::{Element, Task, keyboard, time, window};
|
||||
|
||||
use nix::errno::Errno;
|
||||
use nix::fcntl;
|
||||
use nix::pty::{ForkptyResult, forkpty};
|
||||
use nix::unistd::write;
|
||||
|
||||
use std::fs::File;
|
||||
use std::io::{Read, Write};
|
||||
use std::os::unix::io::{AsFd, OwnedFd};
|
||||
use std::io::{self, Read as _};
|
||||
use std::os::unix::io::{AsFd as _, OwnedFd};
|
||||
use std::process::Command;
|
||||
use std::{error, fmt, thread, time as core_time};
|
||||
|
||||
fn spawn_pty_with_shell(default_shell: String) -> OwnedFd {
|
||||
match unsafe { forkpty(None, None) } {
|
||||
Ok(fork_pty_res) => match fork_pty_res {
|
||||
ForkptyResult::Parent { master, .. } => {
|
||||
set_nonblock(&master);
|
||||
master
|
||||
}
|
||||
ForkptyResult::Child => {
|
||||
Command::new(&default_shell).spawn().unwrap();
|
||||
std::thread::sleep(std::time::Duration::MAX);
|
||||
std::process::exit(0);
|
||||
}
|
||||
},
|
||||
Err(e) => panic!("failed to fork {:?}", e),
|
||||
}
|
||||
}
|
||||
/// whether to enable verbose logging; see `Flags::verbose`
|
||||
static mut VERBOSE: bool = false;
|
||||
|
||||
fn read_from_fd(fd: &OwnedFd) -> Option<Vec<u8>> {
|
||||
let mut read_buffer = [0; 65536];
|
||||
let mut file = File::from(fd.try_clone().unwrap());
|
||||
file.flush();
|
||||
let file = file.read(&mut read_buffer);
|
||||
match file {
|
||||
Ok(file) => Some(read_buffer[..file].to_vec()),
|
||||
Err(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn set_nonblock(fd: &OwnedFd) {
|
||||
let flags = nix::fcntl::fcntl(fd, nix::fcntl::FcntlArg::F_GETFL).unwrap();
|
||||
let mut flags =
|
||||
nix::fcntl::OFlag::from_bits(flags & nix::fcntl::OFlag::O_ACCMODE.bits()).unwrap();
|
||||
flags.set(nix::fcntl::OFlag::O_NONBLOCK, true);
|
||||
nix::fcntl::fcntl(fd, nix::fcntl::FcntlArg::F_SETFL(flags)).unwrap();
|
||||
}
|
||||
/// shell path; see `Flags::shell`
|
||||
static mut SHELL: Option<String> = None;
|
||||
|
||||
/// events to be passed to `Model::update`
|
||||
#[non_exhaustive]
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Msg {
|
||||
Exit,
|
||||
KeyPressed(keyboard::Key),
|
||||
Tick,
|
||||
KeyPressed(iced::keyboard::Key),
|
||||
}
|
||||
|
||||
/// errors for this program
|
||||
#[non_exhaustive]
|
||||
#[derive(Debug)]
|
||||
enum Error {
|
||||
/// out of bounds err while accessing a slice
|
||||
IndexOutOfBounds,
|
||||
/// io error
|
||||
Io(io::Error),
|
||||
/// nix crate error
|
||||
Nix(NixError),
|
||||
/// try to access a `File::from::<OwnedFd>()` without an `OwnedFd`
|
||||
NoFileDescriptor,
|
||||
/// impossible error
|
||||
Unreachable,
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
#[expect(clippy::min_ident_chars, reason = "it's in the docs like that")]
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Nix(nix_error) => return write!(f, "{nix_error}"),
|
||||
Self::Io(io_error) => return write!(f, "{io_error}"),
|
||||
Self::NoFileDescriptor => return write!(f, "no file descriptor specified"),
|
||||
Self::IndexOutOfBounds => return write!(f, "index out of bounds"),
|
||||
Self::Unreachable => return write!(f, "unreachable error, panic"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl error::Error for Error {}
|
||||
|
||||
/// error wrapper for the `nix` crate
|
||||
#[non_exhaustive]
|
||||
#[derive(Debug)]
|
||||
enum NixError {
|
||||
/// an OS error
|
||||
Errno(Errno),
|
||||
/// the error when `OFlags::from_bits(..)` returns `None`
|
||||
UnrecognisedFlag,
|
||||
}
|
||||
|
||||
impl fmt::Display for NixError {
|
||||
#[expect(clippy::min_ident_chars, reason = "it's in the docs like that")]
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match *self {
|
||||
Self::UnrecognisedFlag => return write!(f, "unrecognised flag"),
|
||||
Self::Errno(errno) => return write!(f, "bad fcntl argument. errno: {errno}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// cli flags
|
||||
#[derive(Debug, Clone, Bpaf)]
|
||||
#[bpaf(options)]
|
||||
pub struct Flags {
|
||||
/// path to shell
|
||||
#[bpaf(short('S'), long)]
|
||||
shell: Option<String>,
|
||||
|
||||
/// whether to debug log
|
||||
#[bpaf(short, long)]
|
||||
verbose: bool,
|
||||
|
||||
/// whether to display version: TODO
|
||||
#[expect(dead_code, reason = "TODO")]
|
||||
#[bpaf(short, long)]
|
||||
version: bool,
|
||||
}
|
||||
|
||||
/// represents the terminal emulator\
|
||||
/// example usage:
|
||||
/// ```rust
|
||||
/// iced::application("window title", Model::update, Model::view)
|
||||
/// .theme(Model::theme)
|
||||
/// .default_font(iced::Font::MONOSPACE)
|
||||
/// .decorations(false)
|
||||
/// .subscription(Model::subscription)
|
||||
/// .run()
|
||||
/// ```
|
||||
pub struct Model {
|
||||
screen_buffer: [u8; 65536],
|
||||
screen_buffer_index: usize,
|
||||
/// location of cursor in user input line
|
||||
cursor_index: usize,
|
||||
fd: OwnedFd,
|
||||
stdin: OwnedFd,
|
||||
/// fd of pty
|
||||
fd: Option<OwnedFd>,
|
||||
/// user input line
|
||||
input: String,
|
||||
/// all chars on screen
|
||||
screen_buffer: [u8; 0x4000],
|
||||
/// length of `screen_buffer`'s filled area
|
||||
screen_buffer_index: usize,
|
||||
/// path to shell
|
||||
shell: String,
|
||||
}
|
||||
|
||||
impl Model {
|
||||
fn new(
|
||||
screen_buffer: [u8; 65536],
|
||||
screen_buffer_index: usize,
|
||||
cursor_index: usize,
|
||||
fd: OwnedFd,
|
||||
stdin: OwnedFd,
|
||||
input: String,
|
||||
) -> Self {
|
||||
Model {
|
||||
screen_buffer,
|
||||
screen_buffer_index,
|
||||
cursor_index,
|
||||
fd,
|
||||
stdin,
|
||||
input,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update(&mut self, msg: Msg) -> Task<Msg> {
|
||||
match msg {
|
||||
Msg::Tick => match read_from_fd(&self.fd) {
|
||||
Some(red) => self.update_screen_buffer(&red),
|
||||
None => (),
|
||||
},
|
||||
Msg::KeyPressed(key) => match key {
|
||||
keyboard::Key::Character(c) => {
|
||||
self.input_char(c.chars().nth(0).unwrap());
|
||||
}
|
||||
keyboard::Key::Named(keyboard::key::Named::Enter) => {
|
||||
self.input.push('\n');
|
||||
let mut write_buffer = self.input.as_bytes().to_vec();
|
||||
write(self.fd.as_fd(), &mut write_buffer);
|
||||
self.input = String::new();
|
||||
self.cursor_index = 0;
|
||||
}
|
||||
keyboard::Key::Named(keyboard::key::Named::Space) => {
|
||||
self.input_char(' ');
|
||||
}
|
||||
keyboard::Key::Named(keyboard::key::Named::ArrowLeft) => {
|
||||
if self.cursor_index <= 0 {
|
||||
self.cursor_index = 0;
|
||||
} else {
|
||||
self.cursor_index -= 1;
|
||||
}
|
||||
}
|
||||
keyboard::Key::Named(keyboard::key::Named::ArrowRight) => {
|
||||
if self.cursor_index >= self.input.len() - 1 {
|
||||
self.cursor_index = self.input.len() - 1;
|
||||
} else {
|
||||
self.cursor_index += 1;
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
};
|
||||
iced::Task::<Msg>::none()
|
||||
}
|
||||
|
||||
pub fn view(&self) -> Element<'_, Msg> {
|
||||
let (left, right) =
|
||||
match String::from_utf8(self.screen_buffer[..self.screen_buffer_index].to_vec())
|
||||
.unwrap()
|
||||
.rsplit_once('\n')
|
||||
{
|
||||
Some(tup) => (tup.0.to_string(), tup.1.to_string()),
|
||||
None => (
|
||||
String::new(),
|
||||
String::from_utf8(self.screen_buffer[..self.screen_buffer_index].to_vec())
|
||||
.unwrap()
|
||||
.to_string(),
|
||||
),
|
||||
};
|
||||
scrollable(column![text(left), row![text(right), text(&self.input),]]).into()
|
||||
}
|
||||
|
||||
pub fn theme(&self) -> iced::Theme {
|
||||
iced::Theme::GruvboxDark
|
||||
}
|
||||
|
||||
pub fn subscription(&self) -> iced::Subscription<Msg> {
|
||||
let tick = iced::time::every(iced::time::Duration::new(0, 1)).map(|_| Msg::Tick);
|
||||
let key = keyboard::on_key_press(|key, _| Some(Msg::KeyPressed(key)));
|
||||
iced::Subscription::batch(vec![tick, key])
|
||||
}
|
||||
|
||||
fn update_screen_buffer(&mut self, vec: &Vec<u8>) {
|
||||
let offset = self.screen_buffer_index;
|
||||
for (i, chr) in vec.iter().enumerate() {
|
||||
self.screen_buffer[i + offset] = chr.clone();
|
||||
self.screen_buffer_index += 1;
|
||||
}
|
||||
}
|
||||
|
||||
fn input_char(&mut self, c: char) {
|
||||
/// applies needed side effects when taking an input char
|
||||
#[expect(
|
||||
clippy::arithmetic_side_effects,
|
||||
reason = "cursor_index is bound checked"
|
||||
)]
|
||||
fn input_char(&mut self, chr: char) {
|
||||
if self.cursor_index == self.input.len() {
|
||||
self.input.push_str(c.to_string().as_str());
|
||||
self.input.push_str(chr.to_string().as_str());
|
||||
} else {
|
||||
self.input.insert(self.cursor_index, c);
|
||||
self.input.insert(self.cursor_index, chr);
|
||||
}
|
||||
self.cursor_index += 1;
|
||||
}
|
||||
|
||||
/// subscription logic for model
|
||||
#[inline]
|
||||
pub fn subscription(&self) -> iced::Subscription<Msg> {
|
||||
let tick = time::every(time::Duration::new(0, 1)).map(|_| {
|
||||
return Msg::Tick;
|
||||
});
|
||||
let key = keyboard::on_key_press(|key, _| {
|
||||
return Some(Msg::KeyPressed(key));
|
||||
});
|
||||
return iced::Subscription::batch(vec![tick, key]);
|
||||
}
|
||||
|
||||
/// theme logic for model
|
||||
#[inline]
|
||||
pub const fn theme(&self) -> iced::Theme {
|
||||
return iced::Theme::GruvboxDark;
|
||||
}
|
||||
/// update logic for model
|
||||
/// TODO fix pattern type mismatch
|
||||
/// TODO add more keys
|
||||
#[inline]
|
||||
#[expect(
|
||||
clippy::arithmetic_side_effects,
|
||||
clippy::wildcard_enum_match_arm,
|
||||
reason = "bounds checked"
|
||||
)]
|
||||
pub fn update(&mut self, msg: Msg) -> Task<Msg> {
|
||||
match msg {
|
||||
Msg::Exit => return window::get_latest().and_then(window::close),
|
||||
Msg::KeyPressed(key) => {
|
||||
match key {
|
||||
keyboard::Key::Character(chr) => match chr.chars().nth(0) {
|
||||
Some(chr) => self.input_char(chr),
|
||||
None => return window::get_latest().and_then(window::close),
|
||||
},
|
||||
keyboard::Key::Named(keyboard::key::Named::Enter) => {
|
||||
self.input.push('\n');
|
||||
let write_buffer = self.input.as_bytes().to_vec();
|
||||
if let Some(fd) = &self.fd {
|
||||
match write(fd.as_fd(), &write_buffer) {
|
||||
Ok(_) => (),
|
||||
Err(error) => print_err(&Error::Nix(NixError::Errno(error))),
|
||||
}
|
||||
}
|
||||
self.input = String::new();
|
||||
self.cursor_index = 0;
|
||||
}
|
||||
keyboard::Key::Named(keyboard::key::Named::Space) => {
|
||||
self.input_char(' ');
|
||||
}
|
||||
keyboard::Key::Named(keyboard::key::Named::ArrowLeft) => {
|
||||
if self.cursor_index == 0 {
|
||||
self.cursor_index = 0;
|
||||
} else {
|
||||
self.cursor_index -= 1;
|
||||
}
|
||||
}
|
||||
keyboard::Key::Named(keyboard::key::Named::ArrowRight) => {
|
||||
if self.cursor_index >= self.input.len() {
|
||||
self.cursor_index = self.input.len();
|
||||
} else {
|
||||
self.cursor_index += 1;
|
||||
}
|
||||
}
|
||||
keyboard::Key::Named(keyboard::key::Named::ArrowUp) => {
|
||||
self.cursor_index = 0;
|
||||
}
|
||||
keyboard::Key::Named(keyboard::key::Named::ArrowDown) => {
|
||||
self.cursor_index = self.input.len();
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
return iced::Task::none();
|
||||
}
|
||||
Msg::Tick => {
|
||||
let red = read_from_option_fd(self.fd.as_ref());
|
||||
match red {
|
||||
Ok(red) => {
|
||||
if let Err(error) = self.update_screen_buffer(&red) {
|
||||
print_err(&error);
|
||||
}
|
||||
}
|
||||
Err(error) => print_err(&error),
|
||||
}
|
||||
return iced::Task::none();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// reads from the pty and adds it to the buffer
|
||||
#[expect(
|
||||
clippy::arithmetic_side_effects,
|
||||
clippy::indexing_slicing,
|
||||
reason = "all is bound checked"
|
||||
)]
|
||||
fn update_screen_buffer(&mut self, vec: &[u8]) -> Result<(), Error> {
|
||||
let offset = self.screen_buffer_index;
|
||||
for (i, chr) in vec.iter().enumerate() {
|
||||
let index = i + offset;
|
||||
if index < self.screen_buffer.len() {
|
||||
self.screen_buffer[index] = *chr;
|
||||
} else {
|
||||
return Err(Error::IndexOutOfBounds);
|
||||
}
|
||||
self.screen_buffer_index += 1;
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
/// view logic for model\
|
||||
/// TODO add wide char support\
|
||||
/// TODO bound check
|
||||
#[inline]
|
||||
#[expect(
|
||||
clippy::arithmetic_side_effects,
|
||||
clippy::indexing_slicing,
|
||||
clippy::string_slice,
|
||||
reason = "TODO"
|
||||
)]
|
||||
pub fn view(&self) -> Element<'_, Msg> {
|
||||
let (left, right) =
|
||||
String::from_utf8_lossy(&self.screen_buffer[..self.screen_buffer_index])
|
||||
.rsplit_once('\n')
|
||||
.map_or_else(
|
||||
|| {
|
||||
return (
|
||||
String::new(),
|
||||
String::from_utf8_lossy(
|
||||
&self.screen_buffer[..self.screen_buffer_index],
|
||||
)
|
||||
.to_string(),
|
||||
);
|
||||
},
|
||||
|tup| {
|
||||
return (tup.0.to_owned(), tup.1.to_owned());
|
||||
},
|
||||
);
|
||||
return scrollable(column![
|
||||
text(left),
|
||||
row![
|
||||
text(right),
|
||||
text(&self.input[..(self.cursor_index)]),
|
||||
if self.cursor_index < self.input.len() {
|
||||
row![
|
||||
if self.input[self.cursor_index..=self.cursor_index] == *" " {
|
||||
text("_").color(iced::Color::from_rgb(f32::MAX, 0.0, 0.0))
|
||||
} else {
|
||||
text(&self.input[self.cursor_index..=self.cursor_index])
|
||||
.color(iced::Color::from_rgb(f32::MAX, 0.0, 0.0))
|
||||
},
|
||||
text(&self.input[(self.cursor_index + 1)..])
|
||||
]
|
||||
} else {
|
||||
row![
|
||||
text(&self.input[self.cursor_index..]),
|
||||
rich_text![
|
||||
span("_")
|
||||
.color(iced::Color::from_rgb(f32::MAX, 0.0, 0.0))
|
||||
.background(iced::Color::from_rgb(f32::MAX, f32::MAX, 0.0))
|
||||
]
|
||||
]
|
||||
}
|
||||
]
|
||||
])
|
||||
.into();
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Model {
|
||||
#[inline]
|
||||
#[expect(clippy::undocumented_unsafe_blocks, reason = "clippy be trippin")]
|
||||
fn default() -> Self {
|
||||
let mut me = Self::new(
|
||||
[0; 65536],
|
||||
0,
|
||||
0,
|
||||
spawn_pty_with_shell("/home/mtgmonkey/.nix-profile/bin/dash".to_string()),
|
||||
std::io::stdin().as_fd().try_clone_to_owned().unwrap(),
|
||||
String::new(),
|
||||
);
|
||||
let mut me = Self {
|
||||
screen_buffer: [0; 0x4000],
|
||||
screen_buffer_index: 0,
|
||||
cursor_index: 0,
|
||||
fd: None,
|
||||
input: String::new(),
|
||||
/// SAFETY call *after* `init()`
|
||||
shell: unsafe { SHELL.clone() }.map_or_else(
|
||||
|| return String::from("/home/mtgmonkey/.nix-profile/bin/dash"),
|
||||
|shell| return shell,
|
||||
),
|
||||
};
|
||||
me.fd = spawn_pty_with_shell(&me.shell).ok();
|
||||
let mut nored = true;
|
||||
while nored {
|
||||
let red = read_from_fd(&me.fd);
|
||||
match red {
|
||||
Some(red) => {
|
||||
nored = false;
|
||||
me.update_screen_buffer(&red);
|
||||
let red = read_from_option_fd(me.fd.as_ref());
|
||||
if let Ok(red) = red {
|
||||
nored = false;
|
||||
if let Err(error) = me.update_screen_buffer(&red) {
|
||||
print_err(&error);
|
||||
}
|
||||
None => (),
|
||||
}
|
||||
}
|
||||
me
|
||||
return me;
|
||||
}
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// call *before* creating a `Model` because `Model::default()` relies on `SHELL`
|
||||
/// call *before* `print_err()` because `print_err()` relies on `VERBOSE`
|
||||
#[inline]
|
||||
#[expect(clippy::undocumented_unsafe_blocks, reason = "clippy be trippin")]
|
||||
pub unsafe fn init(flags: Flags) {
|
||||
unsafe {
|
||||
VERBOSE = flags.verbose;
|
||||
}
|
||||
unsafe {
|
||||
SHELL = flags.shell;
|
||||
}
|
||||
}
|
||||
|
||||
/// spawns a pty with the specified shell program
|
||||
#[expect(clippy::single_call_fn, reason = "abstraction")]
|
||||
fn spawn_pty_with_shell(default_shell: &str) -> Result<OwnedFd, Error> {
|
||||
// SAFETY: always safe unless the OS is out of ptys
|
||||
// so it is always safe
|
||||
match unsafe { forkpty(None, None) } {
|
||||
Ok(fork_pty_res) => match fork_pty_res {
|
||||
ForkptyResult::Parent { master, .. } => {
|
||||
if let Err(error) = set_nonblock(&master) {
|
||||
return Err(error);
|
||||
}
|
||||
return Ok(master);
|
||||
}
|
||||
ForkptyResult::Child => {
|
||||
if let Err(error) = Command::new(default_shell).spawn() {
|
||||
return Err(Error::Io(error));
|
||||
}
|
||||
thread::sleep(core_time::Duration::MAX);
|
||||
return Err(Error::Unreachable);
|
||||
}
|
||||
},
|
||||
Err(error) => return Err(Error::Nix(NixError::Errno(error))),
|
||||
}
|
||||
}
|
||||
|
||||
/// reads from an `&OwnedFd`
|
||||
/// TODO check bounds
|
||||
#[expect(
|
||||
clippy::single_call_fn,
|
||||
clippy::indexing_slicing,
|
||||
reason = "abstraction"
|
||||
)]
|
||||
fn read_from_fd(fd: &OwnedFd) -> Result<Vec<u8>, Error> {
|
||||
let mut read_buffer = [0; 0x4000];
|
||||
#[expect(clippy::unwrap_used, reason = "platform-specific but fine")]
|
||||
let mut file = File::from(fd.try_clone().unwrap());
|
||||
let file = file.read(&mut read_buffer);
|
||||
match file {
|
||||
Ok(file) => return Ok(read_buffer[..file].to_vec()),
|
||||
Err(error) => return Err(Error::Io(error)),
|
||||
}
|
||||
}
|
||||
|
||||
/// reads from an `Option<&OwnedFd>` if it's there
|
||||
fn read_from_option_fd(maybe_fd: Option<&OwnedFd>) -> Result<Vec<u8>, Error> {
|
||||
return maybe_fd.map_or(Err(Error::NoFileDescriptor), |fd| {
|
||||
return read_from_fd(fd);
|
||||
});
|
||||
}
|
||||
|
||||
/// sets a `OwnedFd` as nonblocking.
|
||||
#[expect(clippy::single_call_fn, reason = "abstraction")]
|
||||
fn set_nonblock(fd: &OwnedFd) -> Result<(), Error> {
|
||||
let flags = match fcntl::fcntl(fd, fcntl::FcntlArg::F_GETFL) {
|
||||
Ok(flags) => flags,
|
||||
Err(errno) => return Err(Error::Nix(NixError::Errno(errno))),
|
||||
};
|
||||
let flags = fcntl::OFlag::from_bits(flags & fcntl::OFlag::O_ACCMODE.bits());
|
||||
match flags {
|
||||
Some(mut flags) => {
|
||||
flags.set(fcntl::OFlag::O_NONBLOCK, true);
|
||||
if let Err(errno) = fcntl::fcntl(fd, fcntl::FcntlArg::F_SETFL(flags)) {
|
||||
return Err(Error::Nix(NixError::Errno(errno)));
|
||||
}
|
||||
}
|
||||
None => return Err(Error::Nix(NixError::UnrecognisedFlag)),
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
/// if `VERBOSE` is `true`, logs errors
|
||||
#[inline]
|
||||
#[expect(
|
||||
clippy::print_stdout,
|
||||
clippy::undocumented_unsafe_blocks,
|
||||
reason = "toggleable with VERBOSE option\n
|
||||
clippy be buggin"
|
||||
)]
|
||||
fn print_err(error: &Error) {
|
||||
/// SAFETY the only time VERBOSE is written to should be `init()`
|
||||
if unsafe { VERBOSE } {
|
||||
println!("[ERROR] {error}");
|
||||
}
|
||||
}
|
||||
|
|
48
src/main.rs
48
src/main.rs
|
@ -1,35 +1,27 @@
|
|||
use rust_term::*;
|
||||
//! this crate runs a terminal
|
||||
|
||||
#![expect(
|
||||
clippy::needless_return,
|
||||
clippy::cargo_common_metadata,
|
||||
clippy::blanket_clippy_restriction_lints,
|
||||
clippy::multiple_crate_versions,
|
||||
unused_doc_comments,
|
||||
reason = ""
|
||||
)]
|
||||
|
||||
use rust_term::{Model, flags, init};
|
||||
|
||||
#[expect(clippy::undocumented_unsafe_blocks, reason = "clippy be trippin")]
|
||||
fn main() -> iced::Result {
|
||||
iced::application("test", Model::update, Model::view)
|
||||
/// SAFETY call does occur *before* the initialization of a Model
|
||||
/// SAFETY call does occur *before* any opportunity to call `print_err()`
|
||||
unsafe {
|
||||
init(flags().run());
|
||||
};
|
||||
return iced::application("test", Model::update, Model::view)
|
||||
.theme(Model::theme)
|
||||
.default_font(iced::Font::MONOSPACE)
|
||||
.decorations(false)
|
||||
.subscription(Model::subscription)
|
||||
.run()
|
||||
/*
|
||||
let default_shell = "/home/mtgmonkey/.nix-profile/bin/dash".to_string();
|
||||
let fd = spawn_pty_with_shell(default_shell);
|
||||
let mut write_buffer = "tty\n".as_bytes().to_vec();
|
||||
write(fd.as_fd(), &mut write_buffer);
|
||||
loop {
|
||||
let red = read_from_fd(&fd);
|
||||
match red {
|
||||
Some(red) => print!("{}", String::from_utf8(red).unwrap()),
|
||||
None => {
|
||||
let mut read_buffer = [0; 65536];
|
||||
let mut file = File::from(std::io::stdin().as_fd().try_clone_to_owned().unwrap());
|
||||
file.flush();
|
||||
let file = file.read(&mut read_buffer);
|
||||
println!(
|
||||
"{}",
|
||||
match file {
|
||||
Ok(file) => write(fd.as_fd(), &read_buffer[..file]).unwrap(),
|
||||
Err(_) => 0,
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
*/
|
||||
.run();
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue