| Age | Commit message (Collapse) | Author |
|
Add a nullable `mode` argument like `trigger`. Change `trigger` to make
it non-nullable again.
We can now pass a mode trigger in from FFI and handle it in our Rust
function.
|
|
Actually, now that I think about it, the trigger argument shouldn't be
nullable, since one should always be passed in. But this was really more
of a test to make sure we could do the same for a new `mode` argument,
which will also be a `Trigger`, but which might be null.
|
|
An Option `mode` argument. When `Some`, the function should look for
`trigger` inside the given mode. Otherwise it will use the normal map
scope.
|
|
Our new `Trigger` type groups together the two arguments this function
used to take into a single type.
|
|
This struct will be used as function arguments to the C version of
`run_key_action()`. It will enable us to pass in both a trigger and a
mode context more concisely.
|
|
For better namespacing and local context information. Thank goodness
'cbindgen' supports this.
|
|
A new Make target to build the Rust library. This facilitates handling
of other targets that depend on the library.
|
|
Using `.as_ptr()` on the `CString` didn't work. The C code wouldn't
print anything.
Turns out I needed to use `.into_raw()` in order to:
> [Consume] the CString and [transfer] ownership of the string to a C
> caller.
(https://doc.rust-lang.org/std/ffi/struct.CString.html#method.into_raw)
This allows us to correctly access and print the string in our C code.
Note that we'll still need to free the string using a new Rust function
that should be callable from C, otherwise we'll end up with a memory
leak.
|
|
Ensure that the C code can really print something sent from the Rust
code. Our action string wasn't getting printed out, so I wanted to see
it working with a test string. Need to figure out what I have to do to
get it to work for real now.
|
|
I keep forgetting to add the newline at the end, darn. Wasted a few
minutes of my time, but so cool that it's finally working now! The C
program now correctly prints the corresponding map.
Here's a diff of print debug statements I used to diagnose the problem:
diff --git a/src/cocoa_bridge.rs b/src/cocoa_bridge.rs
index fe65533..9690fe4 100644
--- a/src/cocoa_bridge.rs
+++ b/src/cocoa_bridge.rs
@@ -68,12 +68,14 @@ pub extern "C" fn c_run_key_action(
Some(k) => {
match k.action {
Some(a) => {
+ println!("all good");
CKeyActionResult {
action: a.as_ptr(),
kind: &k.kind,
}
},
None => {
+ println!("Action null");
CKeyActionResult {
action: ptr::null(),
kind: &k.kind,
@@ -82,6 +84,7 @@ pub extern "C" fn c_run_key_action(
}
},
None => {
+ println!("All null");
CKeyActionResult {
action: ptr::null(),
kind: ptr::null(),
@@ -98,11 +101,17 @@ pub extern "C" fn run_key_action(
) -> Option<KeyActionResult> {
let sample_maps = "map <up> k
map <down> j
-map <play><down> works!";
+map <play><down> works!
+";
+
+ println!("{:?}", trigger);
+ assert!(trigger == &[HeadphoneButton::Play, HeadphoneButton::Down]);
// Figure out how to persist this without re-parsing
let map_group = MapGroup::parse(sample_maps).unwrap();
+ println!("{:?}", map_group.maps);
+
let map = map_group.maps.get(trigger);
let mode = map_group.modes.get(trigger);
|
|
|
|
Make a test `includer.c` program that includes the Rust library and
calls our `c_run_key_action()` to see if it actually works. Currently it
doesn't, we get `(null)` printed to stdout.
Add a Makefile with the build command for the C program.
cbindgen.toml:
Remove `KeyActionResult` from exported types, as the `Option` field it
contains caused `gcc` to complain.
cocoa_bridge.rs:
* Comment out all 'cocoa' crate related code as the 'cocoa' code was
interfering with GCC compilation as a result of my not linking to
Cocoa frameworks.
* Add a new test map definition that corresponds with the one we look
for in `includer.c`.
parser.rs:
Add `#[repr(C)]` to `MapKind` because it needs to be exported in the
library and generated into our C header file.
|
|
Add a new `CKeyActionResult` that uses a `char *` instead of an
`Option<CString>`. Make our `c_run_key_action()` return this type
instead of `KeyActionResult`. Include some code to handle the
translation between the two types.
Names for now are just temporary so I can get something working. We'll
re-think proper names once I've tested this manually and have something
somewhat working.
|
|
Add a new wrapper function for `run_key_action` that uses C appropriate
inputs & outputs and calls into our Rusty `run_key_action`. This new
function now correctly gets a header generated for it by 'cbindgen'.
Immense thanks to Jake Goulding on the Rust FFI Omnibus for showing me
how to pass a slice argument from C:
http://jakegoulding.com/rust-ffi-omnibus/slice_arguments/
In order to pass the slice from C, we need to pass a C array and its
length to the function. Cool.
|
|
* Add the 'cbindgen' crate to help us auto-generate a C header for our
exported FFI enums/structs/functions
* Add a `build.rs` to generate the C header using cbindgen
* Add a rough config for 'cbindgen'
* Export everything from the `cocoa_bridge` crate to include the
`KeyActionResult` struct
* Commit the C header generated by 'cbindgen'
Looks promising. We do, however, have some things to correct. We can't
use `Option` in C, for instance, so we'll need to fix that in our
`KeyActionResult`. Similarly, we need to rework the `run_key_action()`
function to make it accessible as a C interface. Right now it returns an
`Option`, which isn't going to work, and I'm not sure if the slice input
translates. That (any maybe more) is why it doesn't get generated by
'cbindgen' into the header output file.
|
|
* Add `#[repr(C)]` on `HeadphoneButton` to hopefully be able to use it
outside Rust
* Add `#[no_mangle]` to `run_key_action()`
* Export `run_key_action()` as a public function
* Build the crate as a static library
(But holy cow, a 19 MB library file?)
|
|
Couldn't figure out how to make my `Action` string a char slice. At
first I changed it to a `CStr` reference, but ran into lifetime errors
when trying to use a reference. Instead ended up making it a `CString`.
Still need to handle errors. My plan is to pass back an error string in
the result struct.
Remains to be seen whether this actually works when calling it over FFI,
but we have the beginnings of something.
|
|
Causes an error as `Action` isn't imported.
|
|
Work out the essential body of the function, which necessitates looking
inside a `MapGroup` and finding a `MapAction` corresponding to the
`Trigger` given in the function argument.
The function will then perform whatever action it can and return a
`KeyActionResult` struct, allowing callers to pick up the baton.
|
|
New `run_key_action()` will be called from Objective C. It will:
* Get the action for a `Map` mapping and return it, so that the
corresponding keys can be pressed in Cocoa/Carbon
* Run a `Command` mapping
* Somehow manage mode scoping in conjunction with the Objective C side
The new `KeyActionResult` allows us to tell Objective C what type of
action should be run (`Map`, `Command`, or `Mode`), along with
additional information (for now just the action keys in the case of a
`Map` mapping).
The question is how to hold on to state, keeping the parsed mappings in
memory on the Rust side when called over FFI.
|
|
Not working currently. Just committing this because I didn't before and
because I'm working in a different direction now.
|
|
It's feeling like creating Cocoa objects in Rust is going to be a pain.
Thinking I might want to make the API a little more black box-y and do
more on the Rust side. These are some notes for ideas about how to do
that.
|
|
Add some more structure to the function that will be called from
Objective C code.
* Give it a name, `parse_mappings` (not very thoroughly considered)
* Initialise some Foundation data structures to mirror our Rust ones
* Add the beginning of a struct that will be the ObjC version of our
`MapGroup` struct, containing `NSDictionary`ies.
* Move the `extern crate` into `lib.rs`, I keep forgetting that's where
they go.
* Add a test that just calls `parse_mappings` to check that the code
compiles.
parser::MapGroup:
Make both its fields public to enable us to access them from
`cocoa_bridge`.
Used this helpful example as a reference for the Rust Cocoa code:
https://github.com/servo/core-foundation-rs/blob/c99c05c/cocoa/examples/hello_world.rs
|
|
My `cocoa` module was conflicting with the `cocoa` crate. Didn't think
about that. Rename it to fix the conflict.
|
|
Add a new `cocoa` module which will hold code to be called from
Objective-C. Currently contains a sketch of a function that will be
called via FFI from Objective-C. Haven't thought of a name for it yet.
For now contains a sample map definition "file" in a hard-coded string.
Tells `MapGroup` to parse the map definitions. Will try to create some
Cocoa objects in the `unsafe` block.
`MapGroup`:
Add `parse()` method. Given a map definition string, this new function
will parse the string into a `MapGroup` and return the result. It will
serve as the entry point to parsing DomeKey map definitions.
Thanks to these references for Combine parser error handling:
- https://github.com/ordian/toml_edit/blob/b02bc3/src/parser/document.rs
- https://github.com/ordian/toml_edit/blob/ee6895f/src/parser/errors.rs
- https://github.com/Marwes/combine/blob/54bcfed/examples/ini.rs
|
|
Put our parsing code in its own module. We'll be creating a new `cocoa`
module and doing this allows us to keep `lib` free as a module
container.
|
|
This will enable us to use Foundation data structures to hopefully
export into a Cocoa project.
|
|
Very important: file must be terminated by a newline in order to parse
correctly because of the way `Action`s are parsed. Will need to correct
this to work for EOF also.
We can now parse a whole file of map and mode definitions!
|
|
Clear out the commented failed attempts from
e96b453f33345ba08c8150192157077f310f1b01.
|
|
Create a new `Definition` item type that groups `Map` and `Mode` into a
single type that we can expect on a parser. This allows us to make a
parser that can parse both modes & maps.
A bunch of failed attempts, but finally managed to get something
working, yay!
|
|
No dice. Getting this error:
thread 'tests::map_group_parses_a_whole_map_file_string' panicked at 'assertion failed: `(left == right)`
left: `Err(Errors { position: PointerOffset(4376730950), errors: [Unexpected(Token('a')), Expected(Borrowed("mode"))] })`,
right: `Ok(MapGroup { maps: {[Down]: MapAction { action: "/bin/echo nothing", kind: Command }, [Play]: MapAction { action: "/usr/bin/say hello", kind: Command }}, modes: {[Down, Up]: {[Play]: MapAction { action: "p", kind: Map }}} })`', src/lib.rs:421:9
I need a way to parse modes and maps in any order. Here the code is
trying to parse first modes, then maps.
|
|
Add a new `Mode` type to parse into, similar to the `Map` type we made
recently. Parse a mode definition, which contains a `MapCollection`.
Modes cannot contain other modes, so we don't bother worrying about
recursive parsers.
|
|
Add support for blank, whitespace, and comment lines between and around
map definition lines.
Fiddled with the existing functions I had for this, but finally managed
to get it working, still using Combine's `examples/ini.rs` as a
reference.
|
|
Managed to get `MapCollection` parsing working, but only for a single
mapping for now. Need to figure out what I'm doing wrong.
Here's the result of my test:
thread 'tests::map_collection_parses_maps' panicked at 'assertion failed: `(left == right)`
left: `Ok({[Up, Down]: MapAction { action: "test", kind: Map }})`,
right: `Ok({[Down]: MapAction { action: "/usr/bin/say \'hello\'", kind: Command }, [Up, Down]: MapAction { action: "test", kind: Map }})`', src/lib.rs:265:9
note: Run with `RUST_BACKTRACE=1` for a backtrace.
test tests::map_collection_parses_maps ... FAILED
At first the assertion in the test wasn't working, failing to compile
with this error:
error[E0369]: binary operation `==` cannot be applied to type `std::collections::HashMap<std::vec::Vec<HeadphoneButton>, MapAction>`
--> src/lib.rs:266:9
|
266 | assert_eq!(result.unwrap(), expected);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: an implementation of `std::cmp::PartialEq` might be missing for `std::collections::HashMap<std::vec::Vec<HeadphoneButton>, MapAction>`
= note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info)
Finally realised that I needed to derive `PartialEq` on `MapAction` to
make it work.
Added a comment parser, based on the `examples/ini.rs` example from
Combine:
fn whitespace<I>() -> impl Parser<Input = I>
where
I: Stream<Item = char>,
I::Error: ParseError<I::Item, I::Range, I::Position>,
{
let comment = (token(';'), skip_many(satisfy(|c| c != '\n'))).map(|_| ());
// Wrap the `spaces().or(comment)` in `skip_many` so that it skips alternating whitespace and
// comments
skip_many(skip_many1(space()).or(comment))
}
Tried writing a test for `comment()`, but couldn't get it to work,
presumably because `comment()` doesn't have any output:
#[test]
fn comment_parses_line_comments() {
let text = "# This is a comment
";
let result = comment().parse(text).map(|t| t.0);
// print!("{:?}", result);
assert_eq!(result, Ok(()));
}
|
|
Parse a map definition line into a `Map`.
Got so stuck on this error:
error[E0277]: the trait bound `(): combine::Parser` is not satisfied
--> src/lib.rs:107:16
|
107 | fn map<I>() -> impl Parser<Input = I, Output = Map>
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `combine::Parser` is not implemented for `()`
|
= note: the return type of a function must have a statically known size
error: aborting due to previous error
For more information about this error, try `rustc --explain E0277`.
Turns out the problem was that I had put a semicolon at the end of the
expression in `map()`. Geez. Took hours to finally see that.
|
|
Add a new `Map` type that includes a field for `trigger`. This allows us
to parse a single map definition line into a single struct, making
parsing more granular than going directly to `MapCollection`.
Rename the existing `Map` type to `MapAction` to support this change.
|
|
Can't get this to work. Right now I'm getting this error when running my
test:
Err(Errors { position: PointerOffset(4522385157), errors:
[Unexpected(Token('m')), Expected(Borrowed("lf newline")),
Expected(Borrowed("whitespace")), Expected(Borrowed("tab"))] })
It's supposed to parse a set of map lines, including whitespace and
comments, into a `MapCollection`.
Right now I'm thinking I should split this up more and make a more
fine-grained parser for just a single map line, then build up to the
collection from there.
|
|
For now just parses a string to a newline. In the future this will have
to be more complicated to deal with `<...>`-style special characters and
escaping (darn).
|
|
Should work with `<play>`, `<Play>`, `<PLAY>`, `<PlAy>`.
|
|
So cool! I love parser combinators.
|
|
Parses a headphone button into the `HeadphoneButton` enum.
|
|
These were early tests to figure out how to parse my tokens.
|
|
This parser reads those tokens and parses them to `MapKind` types. Took
a bit of struggling and researching, but it's so cool to see it working
now!
|
|
Further cleans up the `HashMap` definition in `DKMapGroup`.
|
|
Cleans up the `HashMap` definition in `DKMapGroup`.
|
|
`Trigger` can only be one of three values, so it should really be an
enum. But of course, the actual trigger is a set of these buttons, so we
want a list of trigger buttons as the map hash keys.
|
|
The `kind` has a specific set of things that it can be. It didn't need
to be a `String`.
Fix `DKMapGroup.modes` to ensure that values are map hashes.
Remove `trigger` from `Map` because it didn't make sense to include it
in the `HashMap` value if it's the hash key.
|
|
Imagining how the data should be stored after parsing the map file.
|
|
For parsing.
|
|
$ cargo init --lib
$ rustc --version
rustc 1.28.0 (9634041f0 2018-07-30)
|