Custom Key Events
Asked Answered
C

1

6

How can I send a custom SWT key event such that it results in the typing of that exact literal character without any conversion?

We are making a custom on-screen graphical keyboard that allows for multiple possible layouts - QWERTY being the main one, but several others planned, including ones that visually simulate the iPhone keyboard layout or others that are non-standard relative to the PC.

I would like to be able to send any arbitrary character of my choosing as an event, such that it will result in the typing of that exact character. However, whenever I send an event with a given character, SWT seems to automatically interpret and convert it based on the system's keyboard settings and status, changing it to be capitalized/lowercase or using the shifted symbols depending on whether or not the shift key (the real one on the actual keyboard, for instance) is pressed. For instance, passing the character 'T' through a key event will result in either 't' or 'T' depending on the shift key. It performs similarly with the '4' character, doing either '4' or '$' based on shift.

This is problematic, as I wish to perform the shifting of characters manually, and indeed may not wish to use the same shift characters as the standard QWERTY keyboard in any given case (perhaps, for instance, I want '$' to be the shifted (or 'function' key) version of 'R' on a smaller, cellphone-like keyboard). How can I use these events such that it does not assume the usage of QWERTY keys?

The documentation for the 'character' field in the SWT events says the following:

the character represented by the key that was typed. This is the final character that results after all modifiers have been applied. For example, when the user types Ctrl+A, the character value is 0x01. It is important that applications do not attempt to modify the character value based on a stateMask (such as SWT.CTRL) or the resulting character will not be correct.

This makes me believe that it should not be converting my character as it is doing, as I am commanding it to use my "final" result.

I have created a simple, stand-alone example to demonstrate my issue (please note that this is not how I am doing the actual Keyboard, and is only meant to highlight the key event issue):

screenshot

import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Button;

public class KeyboardIssueExample extends Shell {
    private Text txtTextArea;
    private Composite cmpButtons;
    private Button btnBigA;
    private Button btnBigS;
    private Button btnLittleD;
    private Button btnSix;
    private Button btnSeven;
    private Button btnDollah;
    private Button btnStar;

    private SelectionListener mainListener = new SelectionAdapter() {
        @Override
        public void widgetSelected(SelectionEvent selE) {
            Button b = (Button)selE.widget;
            char c = b.getText().charAt(0);
            sendKey(c);
        }
    };

    /**
     * Launch the application.
     * @param args
     */
    public static void main(String args[]) {
        try {
            Display display = Display.getDefault();
            KeyboardIssueExample shell = new KeyboardIssueExample(display);
            shell.open();
            shell.layout();
            while (!shell.isDisposed()) {
                if (!display.readAndDispatch()) {
                    display.sleep();
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * Create the shell.
     * @param display
     */
    public KeyboardIssueExample(Display display) {
        super(display, SWT.SHELL_TRIM);
        createContents();
    }

    /**
     * Create contents of the shell.
     */
    protected void createContents() {
        setText("SWT Application");
        setSize(450, 300);
        setLayout(new GridLayout());

        txtTextArea = new Text(this, SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL | SWT.CANCEL | SWT.MULTI);
        txtTextArea.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1));

        cmpButtons = new Composite(this, SWT.NONE);
        cmpButtons.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 1, 1));
        GridLayout gl_cmpButtons = new GridLayout(7, false);
        gl_cmpButtons.marginWidth = 0;
        gl_cmpButtons.marginHeight = 0;
        cmpButtons.setLayout(gl_cmpButtons);

        btnBigA = new Button(cmpButtons, SWT.NONE);
        btnBigA.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 1, 1));
        btnBigA.setText("A");

        btnBigS = new Button(cmpButtons, SWT.NONE);
        btnBigS.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 1, 1));
        btnBigS.setText("S");

        btnLittleD = new Button(cmpButtons, SWT.NONE);
        btnLittleD.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 1, 1));
        btnLittleD.setText("d");

        btnSix = new Button(cmpButtons, SWT.NONE);
        btnSix.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 1, 1));
        btnSix.setText("6");

        btnSeven = new Button(cmpButtons, SWT.NONE);
        btnSeven.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 1, 1));
        btnSeven.setText("7");

        btnDollah = new Button(cmpButtons, SWT.NONE);
        btnDollah.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 1, 1));
        btnDollah.setText("$");

        btnStar = new Button(cmpButtons, SWT.NONE);
        btnStar.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 1, 1));
        btnStar.setText("*");

        init();
    }

    private void init() {
        btnBigA.addSelectionListener(mainListener);
        btnBigS.addSelectionListener(mainListener);
        btnLittleD.addSelectionListener(mainListener);
        btnSix.addSelectionListener(mainListener);
        btnSeven.addSelectionListener(mainListener);
        btnDollah.addSelectionListener(mainListener);
        btnStar.addSelectionListener(mainListener);
    }

    @Override
    protected void checkSubclass() {
        // Disable the check that prevents subclassing of SWT components
    }


    private void sendKey(char c) {
        sendKeyEvent(c, SWT.KeyDown);
        sendKeyEvent(c, SWT.KeyUp);
    }

    private void sendKeyEvent(char c, int eventType) {
        txtTextArea.forceFocus();
        Event event = new Event();
        event.type = eventType;
        event.character = c;
        getDisplay().post(event);
    }

}

As you will find, the key events do not respect my own custom usage of capitalization, and instead uses its own. How troublesome.

Castillo answered 3/2, 2014 at 15:34 Comment(5)
+1 Excellent question. Didn't expect SWT to behave this way...Weisberg
@Weisberg Thanks, Baz. Although I'm worried, now - you're the one I usually count on to actually know the answer! =PCastillo
Sorry to disappoint you :/Weisberg
@Weisberg Heh, sorry if I expect too much. I mean it as a compliment of your excellent work.Castillo
Cheers mate, appreciate it ;)Weisberg
C
4

Looking at the source of Display.post for the Mac and for Windows there is a lot of code specific to the key up/down events. This includes calls to OS specific APIs to convert the character and key code you specify to the parameters required by the OS specific method used to generate the key stroke event.

For Windows this code calls VkKeyScan which according to the Microsoft documentation does look at the current keyboard state.

Display does have a postEvent(Event) method which adds to the event queue without changing anything, but this is not public so you would have to access it using reflection. Since it is not part of the official API it may change.

Edit: There is actually an Eclipse bug report here for this. It has been open for 10 years so probably isn't going to be fixed!

Update: There is also a Widget.notifyListeners(eventType, Event) method which could be used to send the key events to a specific control. You will have to create the Event and set the required fields.

Competition answered 4/2, 2014 at 8:17 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.