-
Notifications
You must be signed in to change notification settings - Fork 1
Conversation
Signed-off-by: Robert Vojta <[email protected]>
914bf99
to
0efd4c1
Compare
@TimonPost replied to some comments and ignored docstring comments. This is not something to merge right away, it's something to talk about if it's fine or not, etc. When we agree that it's a way to go for now, then I'll write docs, etc. |
okay, I will play around it when I am at home. |
Okay. I already have something on my mind to improve it, but going offline today. Tooths out, I'll be back tomorrow. |
Signed-off-by: Robert Vojta <[email protected]>
f0ef81f
to
d1af8c8
Compare
Signed-off-by: Robert Vojta <[email protected]>
maybe we can put the UNIX mouse encoding into its own trait and implement it for various encodings. Because the parse function is shared logic and can be unified into a single trait. trait MouseEncoding {
fn parse_csi(buffer: &[u8]) -> Result<Option<InternalEvent>>;
}
struct RXVTMouseEncoding;
impl MouseEncoding for RXVTMouseEncoding {
// Buffer does NOT contain: ESC [
fn parse_csi(buffer: &[u8]) -> Result<Option<InternalEvent>> {
// ESC [ Cb ; Cx ; Cy ; M
let s = std::str::from_utf8(&buffer[..buffer.len() - 1])
.map_err(|_| could_not_parse_event_error())?;
let mut split = s.split(';');
let cb = next_parsed::<u16>(&mut split)?;
let cx = next_parsed::<u16>(&mut split)?;
let cy = next_parsed::<u16>(&mut split)?;
let mouse_input_event = match cb {
32 => MouseEvent::Press(MouseButton::Left, cx, cy),
33 => MouseEvent::Press(MouseButton::Middle, cx, cy),
34 => MouseEvent::Press(MouseButton::Right, cx, cy),
35 => MouseEvent::Release(cx, cy),
64 => MouseEvent::Hold(cx, cy),
96 | 97 => MouseEvent::Press(MouseButton::WheelUp, cx, cy),
_ => MouseEvent::Unknown,
};
Ok(Some(InternalEvent::Input(InputEvent::Mouse(
mouse_input_event,
))))
}
}
struct X10MouseEncoding;
impl MouseEncoding for X10MouseEncoding {
// Buffer does NOT contain: ESC [ M
fn parse_csi(buffer: &[u8]) -> Result<Option<InternalEvent>> {
// X10 emulation mouse encoding: ESC [ M CB Cx Cy (6 characters only).
// NOTE (@imdaveho): cannot find documentation on this
if buffer.len() < 3 {
return Ok(None);
}
let cb = buffer[1] as i8 - 32;
// See http://www.xfree86.org/current/ctlseqs.html#Mouse%20Tracking
// The upper left character position on the terminal is denoted as 1,1.
// Subtract 1 to keep it synced with cursor
let cx = buffer[2].saturating_sub(32) as u16 - 1;
let cy = buffer[3].saturating_sub(32) as u16 - 1;
let mouse_input_event = match cb & 0b11 {
0 => {
if cb & 0x40 != 0 {
MouseEvent::Press(MouseButton::WheelUp, cx, cy)
} else {
MouseEvent::Press(MouseButton::Left, cx, cy)
}
}
1 => {
if cb & 0x40 != 0 {
MouseEvent::Press(MouseButton::WheelDown, cx, cy)
} else {
MouseEvent::Press(MouseButton::Middle, cx, cy)
}
}
2 => MouseEvent::Press(MouseButton::Right, cx, cy),
3 => MouseEvent::Release(cx, cy),
_ => MouseEvent::Unknown,
};
Ok(Some(InternalEvent::Input(InputEvent::Mouse(
mouse_input_event,
))))
}
}
struct XTermMouseEncoding;
impl MouseEncoding for XTermMouseEncoding {
// Buffer does NOT contain: ESC [ <
fn parse_csi(buffer: &[u8]) -> Result<Option<InternalEvent>> {
// ESC [ < Cb ; Cx ; Cy (;) (M or m)
if !buffer.ends_with(&[b'm']) && !buffer.ends_with(&[b'M']) {
return Ok(None);
}
let s = std::str::from_utf8(&buffer[..buffer.len() - 1])
.map_err(|_| could_not_parse_event_error())?;
let mut split = s.split(';');
let cb = next_parsed::<u16>(&mut split)?;
// See http://www.xfree86.org/current/ctlseqs.html#Mouse%20Tracking
// The upper left character position on the terminal is denoted as 1,1.
// Subtract 1 to keep it synced with cursor
let cx = next_parsed::<u16>(&mut split)? - 1;
let cy = next_parsed::<u16>(&mut split)? - 1;
let input_event = match cb {
0..=2 | 64..=65 => {
let button = match cb {
0 => MouseButton::Left,
1 => MouseButton::Middle,
2 => MouseButton::Right,
64 => MouseButton::WheelUp,
65 => MouseButton::WheelDown,
_ => unreachable!(),
};
match buffer.last().unwrap() {
b'M' => InputEvent::Mouse(MouseEvent::Press(button, cx, cy)),
b'm' => InputEvent::Mouse(MouseEvent::Release(cx, cy)),
_ => InputEvent::Unknown,
}
}
32 => InputEvent::Mouse(MouseEvent::Hold(cx, cy)),
3 => InputEvent::Mouse(MouseEvent::Release(cx, cy)),
_ => InputEvent::Unknown,
};
Ok(Some(InternalEvent::Input(input_event)))
}
} |
I am not sure about the manual threading work. To fix the current bugs and problems this is okay with me. For a longer-term solution, we definitely need to do something else. I propose to go on with this design. Then we can come back to my proposed design which fixes some of those issues with a new API. This new API is going to be created once all crates are merged. Most of your code can be re-used like: the abstractions like |
Can you elaborate? I don't know what you mean with this. It's already there. See: crossterm-input/src/input/unix_input.rs Lines 115 to 117 in b594d9c
This PR changes it in a way that there's just one thread no matter how many |
trait MouseEncoding {
fn parse_csi(buffer: &[u8]) -> Result<Option<InternalEvent>>;
}
trait ParseCsi {
fn parse_csi(buffer: &[u8]) -> Result<Option<InternalEvent>>;
} ... my personal opinion is that we should rewrite parsing completely to make it maintainable and easily readable. And if we will do this, why do we need to change it in this way now? Am I missing something? |
I mean, working with receivers, senders, mutexes, locks is just a pain. I understand we need it. So that's why I don't care. It is more the idea behind it, and we are going to fix it in future versions so I am okay with it to merge it now. You are not missing something, yea we are going to do the rewrite, and if you think it is to much work, then just leave it like it is. It was just an idea to improve the code. |
Signed-off-by: Robert Vojta <[email protected]>
Signed-off-by: Robert Vojta <[email protected]>
Signed-off-by: Robert Vojta <[email protected]>
Signed-off-by: Robert Vojta <[email protected]>
Signed-off-by: Robert Vojta <[email protected]>
Here's the // TEMPORARY IN THIS PR - IT WONT BE PART OF THE FINAL PR
#![allow(dead_code)]
use std::io::{stdout, Write};
use crossterm_input::{InputEvent, KeyEvent, MouseEvent, RawScreen, Result, TerminalInput};
//
// This is sample `crossterm_cursor::pos_raw` implementation we will have to use.
//
// Once the crates will be merged, `pos_raw` will gain access to `internal_event_receiver()`
// function and thus we can drop `InputEvent::CursorPosition` and use `InternalEvent::CursorPosition`
// to hide this implementation detail from the user.
//
fn pos_raw() -> Result<(u16, u16)> {
let input = TerminalInput::new();
let mut reader = input.read_sync();
// Write command
let mut stdout = stdout();
stdout.write_all(b"\x1B[6n")?;
stdout.flush()?;
loop {
if let Some(InputEvent::CursorPosition(x, y)) = reader.next() {
return Ok((x, y));
}
}
}
fn async_test() -> Result<()> {
let input = TerminalInput::new();
let _raw = RawScreen::into_raw_mode()?;
let mut reader = input.read_async();
input.enable_mouse_mode()?;
loop {
if let Some(event) = reader.next() {
match event {
InputEvent::Keyboard(KeyEvent::Esc) => break,
InputEvent::Keyboard(KeyEvent::Char('c')) => println!("Cursor: {:?}", pos_raw()),
InputEvent::Mouse(mouse) => {
match mouse {
MouseEvent::Press(_, x, y) => println!("Press: {}x{}", x, y),
MouseEvent::Hold(x, y) => println!("Move: {}x{}", x, y),
MouseEvent::Release(x, y) => println!("Release: {}x{}", x, y),
_ => {}
};
}
InputEvent::CursorPosition(_, _) => {}
e => {
println!("Event: {:?}", e);
}
};
}
std::thread::sleep(std::time::Duration::from_millis(100));
println!(".");
}
input.disable_mouse_mode()?;
Ok(())
}
fn sync_test() -> Result<()> {
let input = TerminalInput::new();
let _raw = RawScreen::into_raw_mode()?;
input.enable_mouse_mode()?;
let mut reader = input.read_sync();
loop {
if let Some(event) = reader.next() {
match event {
InputEvent::Keyboard(KeyEvent::Esc) => break,
InputEvent::Keyboard(KeyEvent::Char('c')) => println!("Cursor: {:?}", pos_raw()),
InputEvent::Mouse(mouse) => {
match mouse {
MouseEvent::Press(_, x, y) => println!("Press: {}x{}", x, y),
MouseEvent::Hold(x, y) => println!("Move: {}x{}", x, y),
MouseEvent::Release(x, y) => println!("Release: {}x{}", x, y),
_ => {}
};
}
InputEvent::CursorPosition(_, _) => {}
e => {
println!("Event: {:?}", e);
}
};
}
println!(".");
}
input.disable_mouse_mode()?;
Ok(())
}
fn main() -> Result<()> {
// async_test()
sync_test()
} |
Signed-off-by: Robert Vojta <[email protected]>
Signed-off-by: Robert Vojta <[email protected]>
Signed-off-by: Robert Vojta <[email protected]>
@TimonPost addressed all comments, final review please. Changes I made:
What's way more important:
Once approved, squashed & merged, I'll update the |
Just a note for me - check the rxvt mouse csi parser, if top left is 0, 0 or panic. Reason: rxvt mouse parsing wasn't modified in #7 (others x10, xterm, ...) were. So I did add -1 in this PR to make it sync. But rather double check than introduce another bug. |
Tested Mint Linux: Sync ReaderWorked:
Failed:
Async ReaderWorked:
Failed:
|
Thanks, will check what's wrong. What do you mean with Fn keys working & also not working? |
Signed-off-by: Robert Vojta <[email protected]>
Can you be more specific in those two cases which doesn't work for you?
|
Following works on Ubuntu & Terminal, clean install.
@TimonPost can you try with the latest commit and if it doesn't work for you, can you say what terminal do you use & exact list of keys which don't work? Thanks. |
What I meant with |
All keys seem to work on linux mint now. So that commit fixed it. |
@zrzka Could you elaborate on the part where you're noticing one additional thread per Do you know why? On a separate note, I like how you're making it easier by having a single entity handle reading from the I guess I'm still playing catch up to a lot of changes that you and @TimonPost are making so I'm still trying to wrap my head around these issues. I've been trying to find a way to contribute, but things are moving quickly and the API is changing quick -- so it's been tough following the breadcrumbs or, I guess, it's more like the wake / burned rubber of you two moving so quickly 😅
|
The code in the crossterm-input/src/input/unix.rs Lines 161 to 175 in bb53032
Every
There're lot of problems with the Anyway, this PR (still working on it) just fixes all these issues and is kind of temporary work to keep the |
Signed-off-by: Robert Vojta <[email protected]>
Signed-off-by: Robert Vojta <[email protected]>
@zrzka I get that section of code causing the issues. Just to play devil's advocate -- since this is a large breaking change -- why not (Edit: not really an option at this point since this PR is well on it's way. But might be an alternative work considering if we didn't want to add too many new dependencies and break the previous API. But again, this is just a consideration without implementation to see if adding locks would indeed solve the problem...) Also, a bit of further playing around with the current threaded case for crossterm-input/src/input/unix.rs Lines 35 to 43 in bb53032
Perhaps this is why the current implementation is not allowing the spawned thread to finish, and therefore, each time we call Again, since I did the first pass on this module, largely taking the parse_event logic from Termion as a reference point to handle common ANSI sequences -- I'm curious about the previous implementation's shortcomings. As for WinCon support, I also want to see if there is any way to contribute to that -- so just trying to wrap my head around these changes. Thanks. |
Can you elaborate? The only breaking change is that the What else is a breaking change? I'm not aware of any. Yes, there's change in the internal behaviour where we have one reading thread producing
One piece of code does use
The design evolved into something which is not very useful. Why do we provide ability to instantiate unlimited amount of Again, this PR is not about a large breaking change, it's about fixing current bugs. All the input will be rewritten in the (near) future. |
Signed-off-by: Robert Vojta <[email protected]>
Signed-off-by: Robert Vojta <[email protected]>
@zrzka I guess I might have been misunderstood on my comment on "large breaking changes". I meant that a significant shift from my mental model of the library. It is understandable that, to make the project more robust, these refactors need to be done. I just wanted ask and poke to get clarification to help my understanding of how the project is evolving, and separate the aspects of the implementation that 1) are design optimizations for a easier-to-reason-about and reduce bugs and 2) broken implementations. It seems from your comments that this is largely 1) which has led to several bugs and issues that you are currently resolving with this new approach.
I'm assuming initially the reason for Thanks for walking me through your thoughts on the redesign. Looks promising. |
Related to discussion: This PR is related to this issue: crossterm-rs/crossterm#265 (comment) . If you give phase 3 the last issue a look, you will find the tasks for this PR with related issues that this PR is solving. Besides, it solves some problems spoken about in this issue: crossterm-rs/crossterm#257 |
I'm not sure we understand each other in this thread, based on your responses like ...
&
This PR just fixes couple of bugs. The reason is that all the subcrates ( Once merged, merged In other words, this PR is not a new design. There're other issues to use for future discussion how it should look like. |
@zrzka Cool -- we can discuss this later. I guess with a PR title starting with "Refactor" it's easy to confuse the design update vs strictly fixing bugs, but that's not really important. Also further discussion is off-topic at this point. I had wanted to get a sense of your direction taking this approach to fix the bugs you mentioned. I think I have a good sense of that now. |
Cargo.toml
Outdated
@@ -22,4 +22,5 @@ libc = "0.2.51" | |||
crossterm_utils = { version = "0.3.1" } | |||
crossterm_screen = { version = "0.3.1" } | |||
lazy_static = "1.4" | |||
mio = "0.6.19" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
minor version needed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
isn't mio a unix only dependency now?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
By default, it's a caret requirements, which means:
^1.2.3 := >=1.2.3 <2.0.0
^1.2 := >=1.2.0 <2.0.0
I did it with 0.6.19
and didn't test it with < 0.6.19
. So, I'd rather keep it as it is otherwise we will have to test it again with previous versions.
Cargo.toml
Outdated
@@ -22,4 +22,5 @@ libc = "0.2.51" | |||
crossterm_utils = { version = "0.3.1" } | |||
crossterm_screen = { version = "0.3.1" } | |||
lazy_static = "1.4" | |||
mio = "0.6.19" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
isn't mio a unix only dependency now?
Signed-off-by: Robert Vojta <[email protected]>
Signed-off-by: Robert Vojta <[email protected]>
CI is still running, but it already passed on stable. The only remaining job is nightly, which isn't mandatory. Going to merge. |
Changes
Sync
&Async
readersstd::sync::mpsc
channelsTerminalInput::read_line
functionality moved to theInput::read_line
with default implementationUnixInput::read_char
reimplemented withSyncReader
& waiting for theInputEvent::Keyboard(KeyEvent::Char(..))
eventInternalEvent
InputEvent::CursorPosition
(UNIX only)0, 0
parse_event
function to multiple/smaller onesparse_event
functionsFixes crossterm-rs/crossterm#199
Fixes crossterm-rs/crossterm#271
Fixes crossterm-rs/crossterm#253
No longer valid
Keeping for history, comments when this PR was started.
It's a proof of concept for discussion! Nothing to merge, ...
Esc
swallowingcargo run --example foo
(part of this PR)AsyncReader::new
InputEvent::Internal
(see comments inside)