SOLVED Getting keyboard commands in GeDialog()

Hi folks,

I'm wanting to get various CTRL+X key presses in my dialog. I can get keys without the ctrl, and I can get the ctrl without keys, but I can't get any ctrl+key presses. In the dialog's Message() function I've tried variations like:

case BFM_INPUT:
{
BaseContainer KB;
GetInputState(BFM_INPUT_KEYBOARD,BFM_INPUT_CHANNEL,KB);
LONG qualifier = KB.GetLong(BFM_INPUT_QUALIFIER,NULL);
String input = msg.GetString(BFM_INPUT_ASC,"");
}

but none work. They key is always blank if control is pressed.

I've seen a couple of older posts that gave some suggestions, but nothing seemed to work.

Is there a particular way we have to do this? Or does it just not work for our own dialogs?

WP.

Hello @wickedp,

Thank you for reaching out to us. This line in your code,

GetInputState(BFM_INPUT_KEYBOARD, BFM_INPUT_CHANNEL, KB);

strikes me as a bit odd. Why are you passing BFM_INPUT_CHANNEL here as askchannel? Unless I am unaware of some special feature/meaning of this symbol in this context, this call makes little sense to me. You are supposed to pass here the channel, in case of the keyboard, the key, you want to poll. E.g.,

GetInputState(BFM_INPUT_KEYBOARD, KEY_ENTER, bc);
GetInputState(BFM_INPUT_KEYBOARD, static_cast<Int32>(someCharacter), bc);

Then also the qualifier keys should work correctly. When they do not, you could also still just poll the state in succession yourself, similar to the little Python script shown here.

Or could it be that you are here mixing up GetInputEvent and GetInputState? The former will allow you to poll for the current input event (whatever that might be), while the latter will allow you to poll for the truthiness of a certain assumed input event state.

Cheers,
Ferdinand

@ferdinand seems like we might be able to do this:

LONG asciivalue = msg.GetLong(BFM_INPUT_CHANNEL);

which will return the ascii value of the character. Instead of asking for the string, maybe this is a better way?

I probably confused myself on that line of code @ferdinanrd, that said, I've used both ways in previous plugins, and both seem to work. I don't know if it's just dumb luck, or if they built a fail-safe in so that if you pass the flag BFM_INPUT_CHANNEL it returns the value of the KEY_WXYZ instead? Or maybe GetInputState() also sets the BFM_INPUT_QUALIFIER flag (it seems to on mine..), and not just true/false for a particular key. Maybe Maxon could clarify for future reference?

I think the ascii route could work for me.

WP.

Hello @wickedp,

@wickedp said in Getting keyboard commands in GeDialog():

[...] they built a fail-safe in so that if you pass the flag BFM_INPUT_CHANNEL it returns the value of the KEY_WXYZ instead?

It could be. I did not look very closely at the implementation, because all that GUI stuff is usually very laborious to figure out, as you often have to dig around deeply in our GUI and things split into many things in the backend.

In one of the major implementations I did not find any indication that BFM_INPUT_CHANNEL has a special meaning (other than BFM_INPUT_QUALIFIER for example which can be used to poll for any qualifier key). I also searched our own code base and the SDK for instances of GetInputState(.*, BFM_INPUT_CHANNEL, i.e., calls which use it in this way as you did. I could not find anything. But that does not necessarily mean that this does not exist in some dark corner of our GUI backend . Which is why I said in my previous posting:

Unless I am unaware of some special feature/meaning of this symbol in this context [...]

This statement still holds true, as it not easy to decipher the old GUI backend in its entirety. But I would rather lean towards that this is a non-conformant usage, and if it worked, it was probably more an unintended effect.

If you want to query just an arbitrary key + qualifier combination, then you should use GetInputEvent. Find below an example in Python (feel free to ask for a C++ translation if you run into troubles).

Cheers,
Ferdinand

The code:

"""Demonstrates the difference between polling for a specific input event and polling for the 
current event.

This is tailored towards your case of pressing CTRL + X. You can run this in the Script Manager 
(and should either press CTRL + X or other key combinations while executing the script).
"""

import c4d

def main() -> None:
    """
    """
    bc: c4d.BaseContainer = c4d.BaseContainer()

    print ("Querying a specific input state:")
    # Note that you must poll for upper case characters, e.g., X instead of x in your case. ord()
    # is just the Python variant of casting a char to int (the ASCII value of the char).
    c4d.gui.GetInputState(c4d.BFM_INPUT_KEYBOARD, ord ("X"), bc)
    # Test if the queried key is indeed being pressed.
    print (f"{bc[c4d.BFM_INPUT_VALUE] == 1 = }")
    # Test if this did co-occur with a CTRL button press.
    print (f"{bc[c4d.BFM_INPUT_QUALIFIER] == c4d.QUALIFIER_CTRL = }")

    print ("Querying the current input state:")
    bc.FlushAll()
    c4d.gui.GetInputEvent(c4d.BFM_INPUT_KEYBOARD, bc)
    # chr () is the opposite to ord()
    print (f"{bc[c4d.BFM_INPUT_CHANNEL] = } ({chr (bc[c4d.BFM_INPUT_CHANNEL])})")
    print (f"{bc[c4d.BFM_INPUT_QUALIFIER] == c4d.QUALIFIER_CTRL = }")


if __name__ == '__main__':
    main()

This will spit out the following lines when pressing CTRL+X:

Querying a specific input state:
bc[c4d.BFM_INPUT_VALUE] == 1 = True
bc[c4d.BFM_INPUT_QUALIFIER] == c4d.QUALIFIER_CTRL = True
Querying the general input state:
bc[c4d.BFM_INPUT_CHANNEL] = 88 (X)
bc[c4d.BFM_INPUT_QUALIFIER] == c4d.QUALIFIER_CTRL = True

And these lines when pressing CTRL+Y for example:

Querying a specific input state:
bc[c4d.BFM_INPUT_VALUE] == 1 = False
bc[c4d.BFM_INPUT_QUALIFIER] == c4d.QUALIFIER_CTRL = True
Querying the general input state:
bc[c4d.BFM_INPUT_CHANNEL] = 89 (Y)
bc[c4d.BFM_INPUT_QUALIFIER] == c4d.QUALIFIER_CTRL = True

Thanks @ferdinand,

I'll use the KEY_WXYZ as you pointed out, if that's the correct usage, I should use it. And the python code you shared gave me an idea to convert the ASCII numbers to a String() as well. Things now seem to work the way I had intended them to.

I can appreciate not wanting to dig into old interface designs - it's what I'm attempting to rewrite at the moment! 🙂

Solved.

WP.

I didn't follow this topic in detail when it was originally posted, but I now noticed that in several of my plugins I do also use
GetInputState(BFM_INPUT_KEYBOARD, BFM_INPUT_CHANNEL, KB);
in order to check for possible qualifiers being pressed.

I guess this must have been picked up in the old forum, as the code is quite some years old.
It may not have been the intended way, but it seems to have been working all those years. At least till R21, as I haven't worked on more recent versions.

In the old forums there were quite some pieces of "example" code scattered around in posts, and back then it was a way to find out how to use some of the available functionality, due to limited documentation.

Speaking of documentation:
I just noticed that https://developers.maxon.net/docs/Cinema4DCPPSDK/html/class_scene_hook_data.html
lists MouseInput and KeyboardInput both pointing to "Input Events" for their last function argument
[in] msg The keyboard message container. See Input Events.
But the link just points back to the beginning of the page.
Where to find more details about these "Input Events" ???

Hello @c4ds,

thank you for reaching out to us.

GetInputState()

Just as a FYI, while you not might remember me, I was actually already around on the old forum 😉 So, I had a look again, looking at our:

  1. Codebase: Nothing is using BFM_INPUT_CHANNEL in this manner, not even really, really, really old code.
  2. Plugin Café History: Yes, there are some mentions of GetInputState(BFM_INPUT_KEYBOARD, BFM_INPUT_CHANNEL, but these come exclusively from users who
    a. Have problems with retreiveing the pressed key
    b. Not use the method to retreive any speciifc key and are only intersted in modifiers or other data provided in the response container.
  3. I did not find any code examples posted by Maxon using this form.

I do not want to be rude here, but what you are doing is not correct and likely never was. This code floated around on Plugin Café and apparently people were copying each other but the code is at least now simply wrong (when one is interested in polling specific keys).

Yes, you can use GetInputState(BFM_INPUT_KEYBOARD, BFM_INPUT_CHANNEL, bc) and this will not crash or any thing, but it is also meaningless. You could also call GetInputState(BFM_INPUT_KEYBOARD, 123456789, bc) and it would be as meaningful. If you are not interested in a specfic key being pressed, than this will also 'work' for you as the askchannel argument is then irrelevant.

But we should really stop indicating that this is the right way to go, because that is where all the confusion comes from. Find a Python script at the end of the posting which in detail demonstrates this. Please stop indicating that this is a valid approach, as people will read this and then the cycle starts over again.

Input Events Page for the C++ Documentation

Thank you for making me aware of this. The target of these links, page_input_events has long been removed. I still have access to that archived page, and while the page was not too useful, I can only speculate why it has been done in this form. I will fix this this in an upcoming release. In the meantime, you should look at:

  • the BFM Symbol Group: This however does not contain only input event symbols, but all GUI related event symbols. Input event related symbols folllow the form BFM_INPUT_... plus things like the KEY symbols or
  • the Input Events page of the Python docs which is much better. Something like this unfournetely never existed in C++ the old C++ page was entierly different. Maybee it got removed in order to make room for something similar to the Python page, and then things were somehow not carried out?

Long story short, I will provide a meaningful input events page for C++ in the upcoming release. In the meantime I would recommend the Python page.

Cheers,
Ferdinand

The code for GetInputState(BFM_INPUT_KEYBOARD, BFM_INPUT_CHANNEL, bc) being not a meaningful thing:

"""Demonstrates that #BFM_INPUT_CHANNEL does not carry any meaning as the argument #askchannel for
GetInputState.
"""

import c4d

def main() -> None:
    """
    """
    bc: c4d.BaseContainer = c4d.BaseContainer()
    if not (c4d.gui.GetInputState(c4d.BFM_INPUT_KEYBOARD, c4d.BFM_INPUT_CHANNEL, bc)):
        raise RuntimeError("Could not poll input event.")

    # Iterate over the container, there is nothing meaningful in there when you want to query a 
    # specific key.
    print ("\nThe result container:")
    for key, value in bc:
        print (f"key: {key}, value: {value}")
    
    # And here some selected values to be a bit more verbose.
    print ("\nSelected values:")
    print (f"{bc[c4d.BFM_INPUT_CHANNEL] = }") # Will return 1768973153, i.e., itself.
    print (f"{bc[c4d.BFM_INPUT_VALUE] = }") # Will return False.
    print (f"{bc[c4d.BFM_INPUT_ASC] = }") # Will return the empty string.

    # We can of course still use the result container to poll for things that are also evaluated as
    # for example modifier keys (or the mouse position in case of the mouse device).
    print ("\nThe container is still valid for other stuff, but BFM_INPUT_CHANNEL is" +
           "meaningless in that context:")
    print (f"BFM_INPUT_CHANNEL: {bc[c4d.BFM_INPUT_MODIFIERS] = }")

    # But we would not have to use BFM_INPUT_CHANNEL for that, we also just could do this here:
    bc: c4d.BaseContainer = c4d.BaseContainer()
    if not (c4d.gui.GetInputState(c4d.BFM_INPUT_KEYBOARD, 123456789, bc)):
        raise RuntimeError("Could not poll input event.")
    print (f"123456789: {bc[c4d.BFM_INPUT_MODIFIERS] = }")

    # And just for completeness, in case someone is just reading this snippet, here is how you
    # should use these functions:
    print ("\nHow it should be done:")

    bc: c4d.BaseContainer = c4d.BaseContainer()

    # Querying for a specific key with GetInputState.

    print ("\nQuerying a specific input state:")
    # Note that you must poll for upper case characters, e.g., X instead of x.
    if not c4d.gui.GetInputState(c4d.BFM_INPUT_KEYBOARD, ord ("X"), bc):
        raise RuntimeError("Failed to query input events.")
    # Test if the queried key is indeed being pressed.
    print (f"{bc[c4d.BFM_INPUT_VALUE] == 1 = }")
    # Test if this did co-occur with a CTRL button press.
    print (f"{bc[c4d.BFM_INPUT_QUALIFIER] == c4d.QUALIFIER_CTRL = }")

    # Querying for the general state with key with GetInputEvent, i.e., capturing all inputs and
    # not only a specific one.

    print ("\nQuerying the current input state:")
    bc: c4d.BaseContainer = c4d.BaseContainer()
    if not c4d.gui.GetInputEvent(c4d.BFM_INPUT_KEYBOARD, bc):
        raise RuntimeError("Failed to query input events.")

    # Get the key which is currently be pressed as an ASCII value.
    print (f"{bc[c4d.BFM_INPUT_CHANNEL] = } ({chr (bc[c4d.BFM_INPUT_CHANNEL])})")
    # We can still read all the other things which are written into an input event container, as
    # for example a modifier key state.
    print (f"{bc[c4d.BFM_INPUT_QUALIFIER] == c4d.QUALIFIER_CTRL = }")


if __name__ == '__main__':
    main()

An example output for pressing CTRL + X while running the script:


The result container:
key: 1768973430, value: 1801812322
key: 1768973153, value: 1768973153
key: 1768976737, value: 2
key: 1768975727, value: 258
key: 1768978017, value: 0
key: 1768977985, value: 0.0
key: 1801548643, value:

Selected values:
bc[c4d.BFM_INPUT_CHANNEL] = 1768973153
bc[c4d.BFM_INPUT_VALUE] = 0
bc[c4d.BFM_INPUT_ASC] = ''

The container is still valid for other stuff, but BFM_INPUT_CHANNEL ismeaningless in that context:
BFM_INPUT_CHANNEL: bc[c4d.BFM_INPUT_MODIFIERS] = 258
123456789: bc[c4d.BFM_INPUT_MODIFIERS] = 258

How it should be done:

Querying a specific input state:
bc[c4d.BFM_INPUT_VALUE] == 1 = True
bc[c4d.BFM_INPUT_QUALIFIER] == c4d.QUALIFIER_CTRL = True

Querying the current input state:
bc[c4d.BFM_INPUT_CHANNEL] = 88 (X)
bc[c4d.BFM_INPUT_QUALIFIER] == c4d.QUALIFIER_CTRL = True
>>>