-
Notifications
You must be signed in to change notification settings - Fork 20
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Get software keyboard input without max byte size parameter #157
Get software keyboard input without max byte size parameter #157
Conversation
ab8d86d
to
65f7d26
Compare
Wow! This re-implementation looks quite nice. I must say I'm not against the "amounts" of unsafe code present here, since it manages to strengthen the API quite a lot and give us more control over the internal implementation (as I said in the past, especially regarding I'll try a deep dive review once I manage to get some time. |
890e226
to
e593abd
Compare
Is this much unsafe code worth such a small quality of life change? Maybe!
Isolating unsafe code is still important even in unsafe functions
This lets us mark the function as safe since it can only be called with an initialized `SwkbdState`
0e0b9ae
to
757afe9
Compare
0eb4efe
to
73e5628
Compare
Okay, I think this PR is in a pretty good state now and I probably won't need to push any more changes. I'm not sure what the nightly test failures are about though. They don't seem related to anything actually going on in the code here. |
73e5628
to
cfd276c
Compare
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.
Mostly LGTM, had some small comments / questions about some of the implementation but a lot of it is just musing about future possibilities and I don't think anything blocking.
IMO probably would be fine to merge as-is with one more approval
struct MessageCallbackData { | ||
extra: *mut SwkbdExtra, | ||
swkbd_shared_mem_ptr: *mut libc::c_void, | ||
} |
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.
Is this something that we might eventually be able to generalize to a MessageCallbackData<T>
where T = SwkbdExtra
in this case? I know some people have been interested in general-purpose svc
helpers to work with system calls that don't have "nice" APIs in libctru
, so just wondering how generic this code is.
Definitely not asking for something like that in this PR, but just wondering if this PR might provide a good starting point from which that could be built. I believe some of the ir_user
module by @AzureMarker also did a bit of svc
memory allocation and stuff but I am not familiar with the specifics so not sure if there is any overlap there.
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.
I only made this struct because libctru
doesn't include a pointer to the shared memory block in SwkbdExtra
like it ought to, and instead uses a static
to pass the pointer into the APT message callback. I decided there's no reason we have to do the same in our implementation.
I've never messed with any other APT message calls, so I'm not sure if this specific struct would be helpful outside of this particular context.
ctru-rs/src/applets/swkbd.rs
Outdated
shared_mem_size += | ||
(std::mem::size_of::<u16>() * (swkbd.max_text_len as usize + 1) + 3) & !3; |
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.
I assume the + 3) & !3
is an alignment thing (align to usize
, maybe)? does it make sense to have something like this just to make the math more obvious?
let max_len = swkbd.max_text_len as usize + 1;
shared_mem_size += (size_of::<u16>() * max_len).next_multiple_of(size_of::<usize>());
// etc.
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.
Sure, that might make more sense. I'm honestly not very fluent in bitwise math, so I just kinda translated the C code directly without getting too fancy about interpreting its meaning.
if swkbd.save_state_flags & (1 << 0) != 0 { | ||
swkbd.status_offset = shared_mem_size as _; | ||
shared_mem_size += std::mem::size_of::<SwkbdStatusData>(); | ||
} | ||
|
||
if swkbd.save_state_flags & (1 << 1) != 0 { | ||
swkbd.learning_offset = shared_mem_size as _; | ||
shared_mem_size += std::mem::size_of::<SwkbdLearningData>(); | ||
} |
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.
It would be nice if we could use bitflags
for this but I think this struct is defined by ctru_sys
right? so maybe there's no great option but maybe defining consts for the bitshifts would be useful
ctru-rs/src/applets/swkbd.rs
Outdated
shared_mem_size += std::mem::size_of::<SwkbdLearningData>(); | ||
} | ||
|
||
shared_mem_size = (shared_mem_size + 0xFFF) & !0xFFF; |
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.
similar comment, could this be
swkbd.shared_memory_size = shared_mem_size.next_multiple_of(0x1000);
swkbd.shared_memory_size = shared_mem_size; | ||
|
||
// Allocate shared mem | ||
let swkbd_shared_mem_ptr = unsafe { libc::memalign(0x1000, shared_mem_size) }; |
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.
huh, I was thinking it would be nice to use Rust alloc
for this but I guess it's passed to C immediately anyway so there's not really a compelling reason to do that that I can think of.
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.
I did think about using std::alloc
, but as you said the memory block is only being manipulated by C code anyway and so it's more straightforward to just use memalign
and free
|
||
if swkbd.text_length > 0 { | ||
let text16 = unsafe { | ||
widestring::Utf16Str::from_slice_unchecked(std::slice::from_raw_parts( |
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.
I assume the _unchecked
here means that we are trusting either the OS or libctru
to give us a valid string, right? Just wondering if an extra check here might be worth a little bit of safety
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.
Yeah, we're trusting the Software Keyboard to give us proper UTF-16 text here and I don't think there's any reason to suspect it would fail at that job
swkbd_shared_mem_ptr.add(swkbd.learning_offset as _).cast(), | ||
extra.learning_data, | ||
1, | ||
) |
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.
nit: might increase readibility here to use the method form:
extra.learning_data.copy_from_nonoverlapping(
swkbd_shared_mem_ptr.add(swkbd.learning_offset as _).cast(),
1,
);
}; | ||
} | ||
|
||
unsafe { libc::free(swkbd_shared_mem_ptr) }; |
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.
Allocating and freeing all the options every time is probably fine, and I don't think we need to optimize right now — but I wonder if we could also have more of a Builder
API pattern that sets all the sizing options up front and allocates just once, which would probably make this call cheaper.
Don't want to prematurely optimize though since swkbd isn't likely to be a hot path for most apps (I hope, lol!)
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.
Yeah, calling the software keyboard involves a lot of setup no matter how you slice it. I'm not sure if it would be worth trying to reduce allocations here, especially since any changes to the software keyboard config would require recomputing and reallocating the memory block anyway.
// A reimplementation of `swkbdMessageCallback` from `libctru/source/applets/swkbd.c`. | ||
// This is only needed because the original function is private to libctru, so we can't | ||
// simply reuse their version | ||
#[deny(unsafe_op_in_unsafe_fn)] |
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.
❤️❤️❤️
I have sometimes considered if we should consider denying as a library lint but I think it would be a nontrivial amount of work so never bothered to try
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.
I am in favour of this as well! Unsafe blocks should be marked only when necessary and not all unsafe functions use unsafe code (especially around here...).
Also while we're messing around in here, what do you guys think about renaming |
17f07c5
to
1446583
Compare
Works for me |
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.
LGTM. The only things I'd like to see improved are the alignment calculations that @ian-h-chamberlain pointed out. Other than that, it's fine.
Apparently |
Needed after rust3ds/ctru-rs#157
Ever since I first started messing with the software keyboard from Rust, I've always hated that libctru's
swkbd
API forces you to use a fixed-size buffer in order to receive string data from the keyboard. We've worked around that restriction by just forcing the user to specify a maximum size for the intermediate text buffer, but I've always hated that solution too. And so I randomly decided it's finally time to do something about it.However, it turns out that "doing something about it" involves reimplementing both swkbdInputText and swkbdMessageCallback in Rust and handling a whole bunch of gnarly unsafe code ourselves instead of just leaving all of that nonsense to libctru.
Is it worth introducing this much unsafe code just to solve a small API nitpick? Frankly I'm not entirely sure, but I thought I'd throw this out there and see what you guys think.