├── .gitignore ├── LICENSE ├── README.md ├── archive ├── EventTable.java ├── StyledText.java ├── XKeysym.java └── XWindow.java ├── build ├── LinuxJavaFixes-1.0.0-SNAPSHOT.jar └── javassist-3.25.0-GA.jar ├── pom.xml └── src └── main ├── java ├── KeyEventDemo.java └── net │ └── x11 │ └── patch │ ├── LinuxJavaPatchAgent.java │ ├── SWTEventTableTransformer.java │ └── XKeysymTransformer.java └── resources ├── META-INF └── MANIFEST.MF ├── patch.key.mapping.properties └── swt.key.mapping.properties /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | *bin* 3 | *gen* 4 | *.settings* 5 | .classpath 6 | .project 7 | /target 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Paullo612/LinuxJavaFixes/976cbb74166e7c1ad562f401021af7a1667997d3/LICENSE -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #LinuxJavaFixes 2 | 3 | Simple javaagent to fix problems in linux with non latin hotkeys in gui java applications. 4 | 5 | Aimed to walkaround bug with java gui apps: "Hotkeys not functional in non-latin keyboard layout in 13.10 - 16.04" https://bugs.launchpad.net/unity/+bug/1226962 6 | 7 | ## Swing java apps (IntelliJ Idea, Oracle SQL Developer etc.) 8 | 9 | Copy to any directory 2 files: 10 | 11 | **LinuxJavaFixes-1.0.0-SNAPSHOT.jar** 12 | 13 | **javassist-3.25.0-GA.jar** 14 | 15 | add 16 | 17 | `-javaagent:[path to directory with jar files]/LinuxJavaFixes-1.0.0-SNAPSHOT.jar` 18 | 19 | to java run string 20 | 21 | #### Examples: 22 | 23 | ##### SoapUI 24 | 25 | Add line to **soapui.sh**. 26 | 27 | `JAVA_OPTS="$JAVA_OPTS java -javaagent:[path to directory with jar files]/LinuxJavaFixes-1.0.0-SNAPSHOT.jar` 28 | 29 | ##### Oracle SQL Developer 30 | 31 | Add line to **sqldeveloper/ide/bin/jdk.conf**. 32 | 33 | `AddVMOption -javaagent:[path to directory with jar files]/LinuxJavaFixes-1.0.0-SNAPSHOT.jar` 34 | 35 | ##### IntelliJ Idea 36 | 37 | Add line to **idea64.vmoptions* or *idea.vmoptions** 38 | 39 | `-javaagent:[path to directory with jar files]/LinuxJavaFixes-1.0.0-SNAPSHOT.jar` 40 | 41 | ## Eclipse 42 | 43 | Copy to any directory 2 files: 44 | 45 | **LinuxJavaFixes-1.0.0-SNAPSHOT.jar** 46 | 47 | **javassist-3.25.0-GA.jar** 48 | 49 | Add following line to **eclipse.ini**. 50 | 51 | `-javaagent:[path to directory with jar files]/LinuxJavaFixes-1.0.0-SNAPSHOT.jar=swt` 52 | 53 | ## Advanced part 54 | 55 | ### Modify kaybindings for swing apps in case non russian layout 56 | 57 | If you want another mapping you can create it by yourself: 58 | 59 | - run any app with java vm option `-javaagent:[path]/LinuxJavaFixes-1.0.0-SNAPSHOT.jar=print` 60 | - after that utily begin print to console entered symbol codes using format 61 | 62 | `XKeysymPatchAgent.keysym=[hex code]` 63 | 64 | - then create file using format `[hex code]=[latin code of the same button]` 65 | 66 | #### Example: 67 | 68 | ``` 69 | 6ca=Q 70 | 71 | 6c3=W 72 | ``` 73 | etc. 74 | 75 | - replace hex codes wuth yours 76 | - use following option to run app with custom mapping: 77 | 78 | `-javaagent:[path to directory with jar files]/LinuxJavaFixes-1.0.0-SNAPSHOT.jar=[your mapping file]` 79 | 80 | ### Modify keybindings for swt in case non russian layout 81 | 82 | - add following line to eclipse.ini 83 | 84 | `-javaagent:[path]/LinuxJavaFixes-1.0.0-SNAPSHOT.jar=swt:print` 85 | 86 | - then grab codes and create properties file with mapping 87 | 88 | `[your locale key]=[latin key]` 89 | 90 | - then run eclipse wuth following config 91 | 92 | `-javaagent:[path]/LinuxJavaFixes-1.0.0-SNAPSHOT.jar=swt:[path to your mapping file]` 93 | -------------------------------------------------------------------------------- /archive/EventTable.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2000, 2011 IBM Corporation and others. 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Eclipse Public License v1.0 5 | * which accompanies this distribution, and is available at 6 | * http://www.eclipse.org/legal/epl-v10.html 7 | * 8 | * Contributors: 9 | * IBM Corporation - initial API and implementation 10 | *******************************************************************************/ 11 | package org.eclipse.swt.widgets; 12 | 13 | 14 | import org.eclipse.swt.*; 15 | import org.eclipse.swt.internal.SWTEventListener; 16 | 17 | /** 18 | * Instances of this class implement a simple 19 | * look up mechanism that maps an event type 20 | * to a listener. Multiple listeners for the 21 | * same event type are supported. 22 | */ 23 | 24 | class EventTable { 25 | int [] types; 26 | Listener [] listeners; 27 | int level; 28 | static final int GROW_SIZE = 4; 29 | 30 | public Listener [] getListeners (int eventType) { 31 | if (types == null) return new Listener [0]; 32 | int count = 0; 33 | for (int i=0; i= 0) { 52 | if (types [index] != 0) break; 53 | --index; 54 | } 55 | index++; 56 | if (index == length) { 57 | int [] newTypes = new int [length + GROW_SIZE]; 58 | System.arraycopy (types, 0, newTypes, 0, length); 59 | types = newTypes; 60 | Listener [] newListeners = new Listener [length + GROW_SIZE]; 61 | System.arraycopy (listeners, 0, newListeners, 0, length); 62 | listeners = newListeners; 63 | } 64 | types [index] = eventType; 65 | listeners [index] = listener; 66 | } 67 | 68 | public boolean hooks (int eventType) { 69 | if (types == null) return false; 70 | for (int i=0; i= 0 ? 1 : -1; 90 | try { 91 | for (int i=0; i= 0 ? 1 : -1; 104 | if (compact && level == 0) { 105 | int index = 0; 106 | for (int i=0; i 0) level = -level; 138 | } 139 | types [index] = 0; 140 | listeners [index] = null; 141 | } 142 | 143 | public void unhook (int eventType, Listener listener) { 144 | if (types == null) return; 145 | for (int i=0; i SunToolkit.MAX_BUTTONS_SUPPORTED) { 675 | return; 676 | } 677 | int type = xev.get_type(); 678 | when = xbe.get_time(); 679 | long jWhen = XToolkit.nowMillisUTC_offset(when); 680 | 681 | int x = xbe.get_x(); 682 | int y = xbe.get_y(); 683 | if (xev.get_xany().get_window() != window) { 684 | Point localXY = toLocal(xbe.get_x_root(), xbe.get_y_root()); 685 | x = localXY.x; 686 | y = localXY.y; 687 | } 688 | 689 | if (type == XConstants.ButtonPress) { 690 | //Allow this mouse button to generate CLICK event on next ButtonRelease 691 | mouseButtonClickAllowed |= XConstants.buttonsMask[lbutton]; 692 | XWindow lastWindow = (lastWindowRef != null) ? ((XWindow)lastWindowRef.get()):(null); 693 | /* 694 | multiclick checking 695 | */ 696 | // if (eventLog.isLoggable(PlatformLogger.FINEST)) eventLog.finest("lastWindow = " + lastWindow + ", lastButton " 697 | // + lastButton + ", lastTime " + lastTime + ", multiClickTime " 698 | // + XToolkit.getMultiClickTime()); 699 | if (lastWindow == this && lastButton == lbutton && (when - lastTime) < XToolkit.getMultiClickTime()) { 700 | clickCount++; 701 | } else { 702 | clickCount = 1; 703 | lastWindowRef = new WeakReference(this); 704 | lastButton = lbutton; 705 | lastX = x; 706 | lastY = y; 707 | } 708 | lastTime = when; 709 | 710 | 711 | /* 712 | Check for popup trigger !! 713 | */ 714 | if (lbutton == getRightButtonNumber() || lbutton > 2) { 715 | popupTrigger = true; 716 | } else { 717 | popupTrigger = false; 718 | } 719 | } 720 | 721 | button = XConstants.buttons[lbutton - 1]; 722 | // 4 and 5 buttons are usually considered assigned to a first wheel 723 | if (lbutton == XConstants.buttons[3] || 724 | lbutton == XConstants.buttons[4]) { 725 | wheel_mouse = true; 726 | } 727 | 728 | // mapping extra buttons to numbers starting from 4. 729 | if ((button > XConstants.buttons[4]) && (!Toolkit.getDefaultToolkit().areExtraMouseButtonsEnabled())){ 730 | return; 731 | } 732 | 733 | if (button > XConstants.buttons[4]){ 734 | button -= 2; 735 | } 736 | modifiers = getModifiers(xbe.get_state(),button,0, type, wheel_mouse); 737 | 738 | if (!wheel_mouse) { 739 | MouseEvent me = new MouseEvent((Component)getEventSource(), 740 | type == XConstants.ButtonPress ? MouseEvent.MOUSE_PRESSED : MouseEvent.MOUSE_RELEASED, 741 | jWhen,modifiers, x, y, 742 | xbe.get_x_root(), 743 | xbe.get_y_root(), 744 | clickCount,popupTrigger,button); 745 | 746 | postEventToEventQueue(me); 747 | 748 | if ((type == XConstants.ButtonRelease) && 749 | ((mouseButtonClickAllowed & XConstants.buttonsMask[lbutton]) != 0) ) // No up-button in the drag-state 750 | { 751 | postEventToEventQueue(me = new MouseEvent((Component)getEventSource(), 752 | MouseEvent.MOUSE_CLICKED, 753 | jWhen, 754 | modifiers, 755 | x, y, 756 | xbe.get_x_root(), 757 | xbe.get_y_root(), 758 | clickCount, 759 | false, button)); 760 | } 761 | 762 | } 763 | else { 764 | if (xev.get_type() == XConstants.ButtonPress) { 765 | MouseWheelEvent mwe = new MouseWheelEvent((Component)getEventSource(),MouseEvent.MOUSE_WHEEL, jWhen, 766 | modifiers, 767 | x, y, 768 | xbe.get_x_root(), 769 | xbe.get_y_root(), 770 | 1,false,MouseWheelEvent.WHEEL_UNIT_SCROLL, 771 | 3,button==4 ? -1 : 1); 772 | postEventToEventQueue(mwe); 773 | } 774 | } 775 | 776 | /* Update the state variable AFTER the CLICKED event post. */ 777 | if (type == XConstants.ButtonRelease) { 778 | /* Exclude this mouse button from allowed list.*/ 779 | mouseButtonClickAllowed &= ~XConstants.buttonsMask[lbutton]; 780 | } 781 | } 782 | 783 | public void handleMotionNotify(XEvent xev) { 784 | super.handleMotionNotify(xev); 785 | XMotionEvent xme = xev.get_xmotion(); 786 | if (isEventDisabled(xev)) { 787 | return; 788 | } 789 | 790 | int mouseKeyState = 0; //(xme.get_state() & (XConstants.buttonsMask[0] | XConstants.buttonsMask[1] | XConstants.buttonsMask[2])); 791 | 792 | //this doesn't work for extra buttons because Xsystem is sending state==0 for every extra button event. 793 | // we can't correct it in MouseEvent class as we done it with modifiers, because exact type (DRAG|MOVE) 794 | // should be passed from XWindow. 795 | final int buttonsNumber = ((SunToolkit)(Toolkit.getDefaultToolkit())).getNumberOfButtons(); 796 | 797 | for (int i = 0; i < buttonsNumber; i++){ 798 | // TODO : here is the bug in WM: extra buttons doesn't have state!=0 as they should. 799 | if ((i != 4) && (i != 5)) { 800 | mouseKeyState = mouseKeyState | (xme.get_state() & XConstants.buttonsMask[i]); 801 | } 802 | } 803 | 804 | boolean isDragging = (mouseKeyState != 0); 805 | int mouseEventType = 0; 806 | 807 | if (isDragging) { 808 | mouseEventType = MouseEvent.MOUSE_DRAGGED; 809 | } else { 810 | mouseEventType = MouseEvent.MOUSE_MOVED; 811 | } 812 | 813 | /* 814 | Fix for 6176814 . Add multiclick checking. 815 | */ 816 | int x = xme.get_x(); 817 | int y = xme.get_y(); 818 | XWindow lastWindow = (lastWindowRef != null) ? ((XWindow)lastWindowRef.get()):(null); 819 | 820 | if (!(lastWindow == this && 821 | (xme.get_time() - lastTime) < XToolkit.getMultiClickTime() && 822 | (Math.abs(lastX - x) < AWT_MULTICLICK_SMUDGE && 823 | Math.abs(lastY - y) < AWT_MULTICLICK_SMUDGE))) { 824 | clickCount = 0; 825 | lastWindowRef = null; 826 | mouseButtonClickAllowed = 0; 827 | lastTime = 0; 828 | lastX = 0; 829 | lastY = 0; 830 | } 831 | 832 | long jWhen = XToolkit.nowMillisUTC_offset(xme.get_time()); 833 | int modifiers = getModifiers(xme.get_state(), 0, 0); 834 | boolean popupTrigger = false; 835 | 836 | Component source = (Component)getEventSource(); 837 | 838 | if (xme.get_window() != window) { 839 | Point localXY = toLocal(xme.get_x_root(), xme.get_y_root()); 840 | x = localXY.x; 841 | y = localXY.y; 842 | } 843 | /* Fix for 5039416. 844 | * According to canvas.c we shouldn't post any MouseEvent if mouse is dragging and clickCount!=0. 845 | */ 846 | if ((isDragging && clickCount == 0) || !isDragging) { 847 | MouseEvent mme = new MouseEvent(source, mouseEventType, jWhen, 848 | modifiers, x, y, xme.get_x_root(), xme.get_y_root(), 849 | clickCount, popupTrigger, MouseEvent.NOBUTTON); 850 | postEventToEventQueue(mme); 851 | } 852 | } 853 | 854 | 855 | // REMIND: need to implement looking for disabled events 856 | public native boolean x11inputMethodLookupString(long event, long [] keysymArray); 857 | native boolean haveCurrentX11InputMethodInstance(); 858 | 859 | private boolean mouseAboveMe; 860 | 861 | public boolean isMouseAbove() { 862 | synchronized (getStateLock()) { 863 | return mouseAboveMe; 864 | } 865 | } 866 | protected void setMouseAbove(boolean above) { 867 | synchronized (getStateLock()) { 868 | mouseAboveMe = above; 869 | } 870 | } 871 | 872 | protected void enterNotify(long window) { 873 | if (window == getWindow()) { 874 | setMouseAbove(true); 875 | } 876 | } 877 | protected void leaveNotify(long window) { 878 | if (window == getWindow()) { 879 | setMouseAbove(false); 880 | } 881 | } 882 | 883 | public void handleXCrossingEvent(XEvent xev) { 884 | super.handleXCrossingEvent(xev); 885 | XCrossingEvent xce = xev.get_xcrossing(); 886 | 887 | // if (eventLog.isLoggable(PlatformLogger.FINEST)) eventLog.finest(xce.toString()); 888 | 889 | if (xce.get_type() == XConstants.EnterNotify) { 890 | enterNotify(xce.get_window()); 891 | } else { // LeaveNotify: 892 | leaveNotify(xce.get_window()); 893 | } 894 | 895 | // Skip event If it was caused by a grab 896 | // This is needed because on displays with focus-follows-mouse on MousePress X system generates 897 | // two XCrossing events with mode != NormalNotify. First of them notifies that the mouse has left 898 | // current component. Second one notifies that it has entered into the same component. 899 | // This looks like the window under the mouse has actually changed and Java handle these events 900 | // accordingly. This leads to impossibility to make a double click on Component (6404708) 901 | XWindowPeer toplevel = getToplevelXWindow(); 902 | if (toplevel != null && !toplevel.isModalBlocked()){ 903 | if (xce.get_mode() != XConstants.NotifyNormal) { 904 | // 6404708 : need update cursor in accordance with skipping Leave/EnterNotify event 905 | // whereas it doesn't need to handled further. 906 | if (xce.get_type() == XConstants.EnterNotify) { 907 | XAwtState.setComponentMouseEntered(getEventSource()); 908 | XGlobalCursorManager.nativeUpdateCursor(getEventSource()); 909 | } else { // LeaveNotify: 910 | XAwtState.setComponentMouseEntered(null); 911 | } 912 | return; 913 | } 914 | } 915 | // X sends XCrossing to all hierarchy so if the edge of child equals to 916 | // ancestor and mouse enters child, the ancestor will get an event too. 917 | // From java point the event is bogus as ancestor is obscured, so if 918 | // the child can get java event itself, we skip it on ancestor. 919 | long childWnd = xce.get_subwindow(); 920 | if (childWnd != XConstants.None) { 921 | XBaseWindow child = XToolkit.windowToXWindow(childWnd); 922 | if (child != null && child instanceof XWindow && 923 | !child.isEventDisabled(xev)) 924 | { 925 | return; 926 | } 927 | } 928 | 929 | // Remember old component with mouse to have the opportunity to send it MOUSE_EXITED. 930 | final Component compWithMouse = XAwtState.getComponentMouseEntered(); 931 | if (toplevel != null) { 932 | if(!toplevel.isModalBlocked()){ 933 | if (xce.get_type() == XConstants.EnterNotify) { 934 | // Change XAwtState's component mouse entered to the up-to-date one before requesting 935 | // to update the cursor since XAwtState.getComponentMouseEntered() is used when the 936 | // cursor is updated (in XGlobalCursorManager.findHeavyweightUnderCursor()). 937 | XAwtState.setComponentMouseEntered(getEventSource()); 938 | XGlobalCursorManager.nativeUpdateCursor(getEventSource()); 939 | } else { // LeaveNotify: 940 | XAwtState.setComponentMouseEntered(null); 941 | } 942 | } else { 943 | ((XComponentPeer) AWTAccessor.getComponentAccessor().getPeer(target)) 944 | .pSetCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); 945 | } 946 | } 947 | 948 | if (isEventDisabled(xev)) { 949 | return; 950 | } 951 | 952 | long jWhen = XToolkit.nowMillisUTC_offset(xce.get_time()); 953 | int modifiers = getModifiers(xce.get_state(),0,0); 954 | int clickCount = 0; 955 | boolean popupTrigger = false; 956 | int x = xce.get_x(); 957 | int y = xce.get_y(); 958 | if (xce.get_window() != window) { 959 | Point localXY = toLocal(xce.get_x_root(), xce.get_y_root()); 960 | x = localXY.x; 961 | y = localXY.y; 962 | } 963 | 964 | // This code tracks boundary crossing and ensures MOUSE_ENTER/EXIT 965 | // are posted in alternate pairs 966 | if (compWithMouse != null) { 967 | MouseEvent me = new MouseEvent(compWithMouse, 968 | MouseEvent.MOUSE_EXITED, jWhen, modifiers, xce.get_x(), 969 | xce.get_y(), xce.get_x_root(), xce.get_y_root(), clickCount, popupTrigger, 970 | MouseEvent.NOBUTTON); 971 | postEventToEventQueue(me); 972 | // eventLog.finest("Clearing last window ref"); 973 | lastWindowRef = null; 974 | } 975 | if (xce.get_type() == XConstants.EnterNotify) { 976 | MouseEvent me = new MouseEvent(getEventSource(), MouseEvent.MOUSE_ENTERED, 977 | jWhen, modifiers, xce.get_x(), xce.get_y(), xce.get_x_root(), xce.get_y_root(), clickCount, 978 | popupTrigger, MouseEvent.NOBUTTON); 979 | postEventToEventQueue(me); 980 | } 981 | } 982 | 983 | public void doLayout(int x, int y, int width, int height) {} 984 | 985 | public void handleConfigureNotifyEvent(XEvent xev) { 986 | Rectangle oldBounds = getBounds(); 987 | 988 | super.handleConfigureNotifyEvent(xev); 989 | // insLog.finer("Configure, {0}, event disabled: {1}",xev.get_xconfigure(), isEventDisabled(xev)); 990 | if (isEventDisabled(xev)) { 991 | return; 992 | } 993 | 994 | // if ( Check if it's a resize, a move, or a stacking order change ) 995 | // { 996 | Rectangle bounds = getBounds(); 997 | if (!bounds.getSize().equals(oldBounds.getSize())) { 998 | postEventToEventQueue(new ComponentEvent(getEventSource(), ComponentEvent.COMPONENT_RESIZED)); 999 | } 1000 | if (!bounds.getLocation().equals(oldBounds.getLocation())) { 1001 | postEventToEventQueue(new ComponentEvent(getEventSource(), ComponentEvent.COMPONENT_MOVED)); 1002 | } 1003 | // } 1004 | } 1005 | 1006 | public void handleMapNotifyEvent(XEvent xev) { 1007 | super.handleMapNotifyEvent(xev); 1008 | // log.fine("Mapped {0}", this); 1009 | if (isEventDisabled(xev)) { 1010 | return; 1011 | } 1012 | ComponentEvent ce; 1013 | 1014 | ce = new ComponentEvent(getEventSource(), ComponentEvent.COMPONENT_SHOWN); 1015 | postEventToEventQueue(ce); 1016 | } 1017 | 1018 | public void handleUnmapNotifyEvent(XEvent xev) { 1019 | super.handleUnmapNotifyEvent(xev); 1020 | if (isEventDisabled(xev)) { 1021 | return; 1022 | } 1023 | ComponentEvent ce; 1024 | 1025 | ce = new ComponentEvent(target, ComponentEvent.COMPONENT_HIDDEN); 1026 | postEventToEventQueue(ce); 1027 | } 1028 | 1029 | private void dumpKeysymArray(XKeyEvent ev) { 1030 | System.out.println(" "+Long.toHexString(XlibWrapper.XKeycodeToKeysym(XToolkit.getDisplay(), ev.get_keycode(), 0))+ 1031 | "\n "+Long.toHexString(XlibWrapper.XKeycodeToKeysym(XToolkit.getDisplay(), ev.get_keycode(), 1))+ 1032 | "\n "+Long.toHexString(XlibWrapper.XKeycodeToKeysym(XToolkit.getDisplay(), ev.get_keycode(), 2))+ 1033 | "\n "+Long.toHexString(XlibWrapper.XKeycodeToKeysym(XToolkit.getDisplay(), ev.get_keycode(), 3))); 1034 | } 1035 | /** 1036 | Return unicode character or 0 if no correspondent character found. 1037 | Parameter is a keysym basically from keysymdef.h 1038 | XXX: how about vendor keys? Is there some with Unicode value and not in the list? 1039 | */ 1040 | int keysymToUnicode( long keysym, int state ) { 1041 | return XKeysym.convertKeysym( keysym, state ); 1042 | } 1043 | int keyEventType2Id( int xEventType ) { 1044 | return xEventType == XConstants.KeyPress ? java.awt.event.KeyEvent.KEY_PRESSED : 1045 | xEventType == XConstants.KeyRelease ? java.awt.event.KeyEvent.KEY_RELEASED : 0; 1046 | } 1047 | static private long xkeycodeToKeysym(XKeyEvent ev) { 1048 | return XKeysym.getKeysym( ev ); 1049 | } 1050 | private long xkeycodeToPrimaryKeysym(XKeyEvent ev) { 1051 | return XKeysym.xkeycode2primary_keysym( ev ); 1052 | } 1053 | static private int primaryUnicode2JavaKeycode(int uni) { 1054 | return (uni > 0? sun.awt.ExtendedKeyCodes.getExtendedKeyCodeForChar(uni) : 0); 1055 | //return (uni > 0? uni + 0x01000000 : 0); 1056 | } 1057 | void logIncomingKeyEvent(XKeyEvent ev) { 1058 | System.out.println("--XWindow.java:handleKeyEvent:" + ev); 1059 | dumpKeysymArray(ev); 1060 | System.out.println("XXXXXXXXXXXXXX javakeycode will be most probably:0x" + Integer.toHexString(XKeysym.getJavaKeycodeOnly(ev))); 1061 | } 1062 | public void handleKeyPress(XEvent xev) { 1063 | super.handleKeyPress(xev); 1064 | XKeyEvent ev = xev.get_xkey(); 1065 | // if (eventLog.isLoggable(PlatformLogger.FIN)) 1066 | System.out.println(ev.toString()); 1067 | if (isEventDisabled(xev)) { 1068 | return; 1069 | } 1070 | handleKeyPress(ev); 1071 | } 1072 | // called directly from this package, unlike handleKeyRelease. 1073 | // un-final it if you need to override it in a subclass. 1074 | final void handleKeyPress(XKeyEvent ev) { 1075 | long keysym[] = new long[2]; 1076 | int unicodeKey = 0; 1077 | keysym[0] = XConstants.NoSymbol; 1078 | 1079 | // if (keyEventLog.isLoggable(PlatformLogger.FINE)) { 1080 | logIncomingKeyEvent( ev ); 1081 | // } 1082 | if ( //TODO check if there's an active input method instance 1083 | // without calling a native method. Is it necessary though? 1084 | haveCurrentX11InputMethodInstance()) { 1085 | if (x11inputMethodLookupString(ev.pData, keysym)) { 1086 | // if (keyEventLog.isLoggable(PlatformLogger.FINE)) { 1087 | System.out.println("--XWindow.java XIM did process event; return; dec keysym processed:"+(keysym[0])+ 1088 | "; hex keysym processed:"+Long.toHexString(keysym[0]) 1089 | ); 1090 | // } 1091 | return; 1092 | }else { 1093 | unicodeKey = keysymToUnicode( keysym[0], ev.get_state() ); 1094 | // if (keyEventLog.isLoggable(PlatformLogger.FINE)) { 1095 | System.out.println("--XWindow.java XIM did NOT process event, hex keysym:"+Long.toHexString(keysym[0])+"\n"+ 1096 | " unicode key:"+Integer.toHexString((int)unicodeKey)); 1097 | // } 1098 | } 1099 | }else { 1100 | // No input method instance found. For example, there's a Java Input Method. 1101 | // Produce do-it-yourself keysym and perhaps unicode character. 1102 | keysym[0] = xkeycodeToKeysym(ev); 1103 | unicodeKey = keysymToUnicode( keysym[0], ev.get_state() ); 1104 | // if (keyEventLog.isLoggable(PlatformLogger.FINE)) { 1105 | System.out.println("--XWindow.java XIM is absent; hex keysym:"+Long.toHexString(keysym[0])+"\n"+ 1106 | " unicode key:"+Integer.toHexString((int)unicodeKey)); 1107 | // } 1108 | } 1109 | // Keysym should be converted to Unicode, if possible and necessary, 1110 | // and Java KeyEvent keycode should be calculated. 1111 | // For press we should post pressed & typed Java events. 1112 | // 1113 | // Press event might be not processed to this time because 1114 | // (1) either XIM could not handle it or 1115 | // (2) it was Latin 1:1 mapping. 1116 | // 1117 | XKeysym.Keysym2JavaKeycode jkc = XKeysym.getJavaKeycode(ev); 1118 | if( jkc == null ) { 1119 | jkc = new XKeysym.Keysym2JavaKeycode(java.awt.event.KeyEvent.VK_UNDEFINED, java.awt.event.KeyEvent.KEY_LOCATION_UNKNOWN); 1120 | } 1121 | 1122 | // Take the first keysym from a keysym array associated with the XKeyevent 1123 | // and convert it to Unicode. Then, even if a Java keycode for the keystroke 1124 | // is undefined, we still have a guess of what has been engraved on a keytop. 1125 | int unicodeFromPrimaryKeysym = keysymToUnicode( xkeycodeToPrimaryKeysym(ev) ,0); 1126 | 1127 | // if (keyEventLog.isLoggable(PlatformLogger.FINE)) { 1128 | // System.out.println(">>>Fire Event:"+ 1129 | // (ev.get_type() == XConstants.KeyPress ? "KEY_PRESSED; " : "KEY_RELEASED; ")+ 1130 | // "jkeycode:decimal="+jkc.getJavaKeycode()+ 1131 | // ", hex=0x"+Integer.toHexString(jkc.getJavaKeycode())+"; "+ 1132 | // " legacy jkeycode: decimal="+XKeysym.getLegacyJavaKeycodeOnly(ev)+ 1133 | // ", hex=0x"+Integer.toHexString(XKeysym.getLegacyJavaKeycodeOnly(ev))+"; " 1134 | // ); 1135 | // } 1136 | 1137 | int jkeyToReturn = XKeysym.getLegacyJavaKeycodeOnly(ev); // someway backward compatible 1138 | int jkeyExtended = jkc.getJavaKeycode() == java.awt.event.KeyEvent.VK_UNDEFINED ? 1139 | primaryUnicode2JavaKeycode( unicodeFromPrimaryKeysym ) : 1140 | jkc.getJavaKeycode(); 1141 | System.out.println("!!!!!!!!!!!!!"); 1142 | System.out.println("unicodeKey="+unicodeKey+" jkeyToReturn="+jkeyToReturn+" ev.get_state()="+ev.get_state()+" ev.getPData()="+ev.getPData()+" (long)(ev.get_keycode())="+(long)(ev.get_keycode())+" unicodeFromPrimaryKeysym="+unicodeFromPrimaryKeysym+" jkeyExtended="+jkeyExtended ); 1143 | 1144 | postKeyEvent(java.awt.event.KeyEvent.KEY_PRESSED, 1145 | ev.get_time(), 1146 | jkeyToReturn, 1147 | (unicodeKey == 0 ? java.awt.event.KeyEvent.CHAR_UNDEFINED : unicodeKey), 1148 | jkc.getKeyLocation(), 1149 | ev.get_state(), ev.getPData(), XKeyEvent.getSize(), (long) (ev.get_keycode()), 1150 | unicodeFromPrimaryKeysym, 1151 | jkeyExtended); 1152 | 1153 | 1154 | if( unicodeKey > 0 ) { 1155 | System.out.println("fire _TYPED on "+unicodeKey); 1156 | postKeyEvent( java.awt.event.KeyEvent.KEY_TYPED, 1157 | ev.get_time(), 1158 | java.awt.event.KeyEvent.VK_UNDEFINED, 1159 | unicodeKey, 1160 | java.awt.event.KeyEvent.KEY_LOCATION_UNKNOWN, 1161 | ev.get_state(),ev.getPData(), XKeyEvent.getSize(), (long)0, 1162 | unicodeFromPrimaryKeysym, 1163 | java.awt.event.KeyEvent.VK_UNDEFINED); 1164 | 1165 | } 1166 | 1167 | 1168 | } 1169 | 1170 | public void handleKeyRelease(XEvent xev) { 1171 | super.handleKeyRelease(xev); 1172 | XKeyEvent ev = xev.get_xkey(); 1173 | // if (eventLog.isLoggable(PlatformLogger.FINE)) 1174 | System.out.println(ev.toString()); 1175 | if (isEventDisabled(xev)) { 1176 | return; 1177 | } 1178 | handleKeyRelease(ev); 1179 | } 1180 | // un-private it if you need to call it from elsewhere 1181 | private void handleKeyRelease(XKeyEvent ev) { 1182 | long keysym[] = new long[2]; 1183 | int unicodeKey = 0; 1184 | keysym[0] = XConstants.NoSymbol; 1185 | 1186 | // if (keyEventLog.isLoggable(PlatformLogger.FINE)) { 1187 | logIncomingKeyEvent( ev ); 1188 | // } 1189 | // Keysym should be converted to Unicode, if possible and necessary, 1190 | // and Java KeyEvent keycode should be calculated. 1191 | // For release we should post released event. 1192 | // 1193 | XKeysym.Keysym2JavaKeycode jkc = XKeysym.getJavaKeycode(ev); 1194 | if( jkc == null ) { 1195 | jkc = new XKeysym.Keysym2JavaKeycode(java.awt.event.KeyEvent.VK_UNDEFINED, java.awt.event.KeyEvent.KEY_LOCATION_UNKNOWN); 1196 | } 1197 | // if (keyEventLog.isLoggable(PlatformLogger.FINE)) { 1198 | System.out.println(">>>Fire Event:"+ 1199 | (ev.get_type() == XConstants.KeyPress ? "KEY_PRESSED; " : "KEY_RELEASED; ")+ 1200 | "jkeycode:decimal="+jkc.getJavaKeycode()+ 1201 | ", hex=0x"+Integer.toHexString(jkc.getJavaKeycode())+"; "+ 1202 | " legacy jkeycode: decimal="+XKeysym.getLegacyJavaKeycodeOnly(ev)+ 1203 | ", hex=0x"+Integer.toHexString(XKeysym.getLegacyJavaKeycodeOnly(ev))+"; " 1204 | ); 1205 | // } 1206 | // We obtain keysym from IM and derive unicodeKey from it for KeyPress only. 1207 | // We used to cache that value and retrieve it on KeyRelease, 1208 | // but in case for example of a dead key+vowel pair, a vowel after a deadkey 1209 | // might never be cached before. 1210 | // Also, switching between keyboard layouts, we might cache a wrong letter. 1211 | // That's why we use the same procedure as if there was no IM instance: do-it-yourself unicode. 1212 | unicodeKey = keysymToUnicode( xkeycodeToKeysym(ev), ev.get_state() ); 1213 | 1214 | // Take a first keysym from a keysym array associated with the XKeyevent 1215 | // and convert it to Unicode. Then, even if Java keycode for the keystroke 1216 | // is undefined, we still will have a guess of what was engraved on a keytop. 1217 | int unicodeFromPrimaryKeysym = keysymToUnicode( xkeycodeToPrimaryKeysym(ev) ,0); 1218 | 1219 | int jkeyToReturn = XKeysym.getLegacyJavaKeycodeOnly(ev); // someway backward compatible 1220 | int jkeyExtended = jkc.getJavaKeycode() == java.awt.event.KeyEvent.VK_UNDEFINED ? 1221 | primaryUnicode2JavaKeycode( unicodeFromPrimaryKeysym ) : 1222 | jkc.getJavaKeycode(); 1223 | postKeyEvent( java.awt.event.KeyEvent.KEY_RELEASED, 1224 | ev.get_time(), 1225 | jkeyToReturn, 1226 | (unicodeKey == 0 ? java.awt.event.KeyEvent.CHAR_UNDEFINED : unicodeKey), 1227 | jkc.getKeyLocation(), 1228 | ev.get_state(),ev.getPData(), XKeyEvent.getSize(), (long)(ev.get_keycode()), 1229 | unicodeFromPrimaryKeysym, 1230 | jkeyExtended); 1231 | 1232 | 1233 | } 1234 | 1235 | /* 1236 | * XmNiconic and Map/UnmapNotify (that XmNiconic relies on) are 1237 | * unreliable, since mapping changes can happen for a virtual desktop 1238 | * switch or MacOS style shading that became quite popular under X as 1239 | * well. Yes, it probably should not be this way, as it violates 1240 | * ICCCM, but reality is that quite a lot of window managers abuse 1241 | * mapping state. 1242 | */ 1243 | int getWMState() { 1244 | if (stateChanged) { 1245 | stateChanged = false; 1246 | WindowPropertyGetter getter = 1247 | new WindowPropertyGetter(window, XWM.XA_WM_STATE, 0, 1, false, 1248 | XWM.XA_WM_STATE); 1249 | try { 1250 | int status = getter.execute(); 1251 | if (status != XConstants.Success || getter.getData() == 0) { 1252 | return savedState = XUtilConstants.WithdrawnState; 1253 | } 1254 | 1255 | if (getter.getActualType() != XWM.XA_WM_STATE.getAtom() && getter.getActualFormat() != 32) { 1256 | return savedState = XUtilConstants.WithdrawnState; 1257 | } 1258 | savedState = (int)Native.getCard32(getter.getData()); 1259 | } finally { 1260 | getter.dispose(); 1261 | } 1262 | } 1263 | return savedState; 1264 | } 1265 | 1266 | /** 1267 | * Override this methods to get notifications when top-level window state changes. The state is 1268 | * meant in terms of ICCCM: WithdrawnState, IconicState, NormalState 1269 | */ 1270 | protected void stateChanged(long time, int oldState, int newState) { 1271 | } 1272 | 1273 | @Override 1274 | public void handlePropertyNotify(XEvent xev) { 1275 | super.handlePropertyNotify(xev); 1276 | XPropertyEvent ev = xev.get_xproperty(); 1277 | if (ev.get_atom() == XWM.XA_WM_STATE.getAtom()) { 1278 | // State has changed, invalidate saved value 1279 | stateChanged = true; 1280 | stateChanged(ev.get_time(), savedState, getWMState()); 1281 | } 1282 | } 1283 | 1284 | public void reshape(Rectangle bounds) { 1285 | reshape(bounds.x, bounds.y, bounds.width, bounds.height); 1286 | } 1287 | 1288 | public void reshape(int x, int y, int width, int height) { 1289 | if (width <= 0) { 1290 | width = 1; 1291 | } 1292 | if (height <= 0) { 1293 | height = 1; 1294 | } 1295 | this.x = x; 1296 | this.y = y; 1297 | this.width = width; 1298 | this.height = height; 1299 | xSetBounds(x, y, width, height); 1300 | // Fixed 6322593, 6304251, 6315137: 1301 | // XWindow's SurfaceData should be invalidated and recreated as part 1302 | // of the process of resizing the window 1303 | // see the evaluation of the bug 6304251 for more information 1304 | validateSurface(); 1305 | layout(); 1306 | } 1307 | 1308 | public void layout() {} 1309 | 1310 | boolean isShowing() { 1311 | return visible; 1312 | } 1313 | 1314 | boolean isResizable() { 1315 | return true; 1316 | } 1317 | 1318 | boolean isLocationByPlatform() { 1319 | return false; 1320 | } 1321 | 1322 | void updateSizeHints() { 1323 | updateSizeHints(x, y, width, height); 1324 | } 1325 | 1326 | void updateSizeHints(int x, int y, int width, int height) { 1327 | long flags = XUtilConstants.PSize | (isLocationByPlatform() ? 0 : (XUtilConstants.PPosition | XUtilConstants.USPosition)); 1328 | if (!isResizable()) { 1329 | // log.finer("Window {0} is not resizable", this); 1330 | flags |= XUtilConstants.PMinSize | XUtilConstants.PMaxSize; 1331 | } else { 1332 | // log.finer("Window {0} is resizable", this); 1333 | } 1334 | setSizeHints(flags, x, y, width, height); 1335 | } 1336 | 1337 | void updateSizeHints(int x, int y) { 1338 | long flags = isLocationByPlatform() ? 0 : (XUtilConstants.PPosition | XUtilConstants.USPosition); 1339 | if (!isResizable()) { 1340 | // log.finer("Window {0} is not resizable", this); 1341 | flags |= XUtilConstants.PMinSize | XUtilConstants.PMaxSize | XUtilConstants.PSize; 1342 | } else { 1343 | // log.finer("Window {0} is resizable", this); 1344 | } 1345 | setSizeHints(flags, x, y, width, height); 1346 | } 1347 | 1348 | void validateSurface() { 1349 | if ((width != oldWidth) || (height != oldHeight)) { 1350 | doValidateSurface(); 1351 | 1352 | oldWidth = width; 1353 | oldHeight = height; 1354 | } 1355 | } 1356 | 1357 | final void doValidateSurface() { 1358 | SurfaceData oldData = surfaceData; 1359 | if (oldData != null) { 1360 | surfaceData = graphicsConfig.createSurfaceData(this); 1361 | oldData.invalidate(); 1362 | } 1363 | } 1364 | 1365 | public SurfaceData getSurfaceData() { 1366 | return surfaceData; 1367 | } 1368 | 1369 | public void dispose() { 1370 | SurfaceData oldData = surfaceData; 1371 | surfaceData = null; 1372 | if (oldData != null) { 1373 | oldData.invalidate(); 1374 | } 1375 | XToolkit.targetDisposedPeer(target, this); 1376 | destroy(); 1377 | } 1378 | 1379 | public Point getLocationOnScreen() { 1380 | synchronized (target.getTreeLock()) { 1381 | Component comp = target; 1382 | 1383 | while (comp != null && !(comp instanceof Window)) { 1384 | comp = AWTAccessor.getComponentAccessor().getParent(comp); 1385 | } 1386 | 1387 | // applets, embedded, etc - translate directly 1388 | // XXX: override in subclass? 1389 | if (comp == null || comp instanceof sun.awt.EmbeddedFrame) { 1390 | return toGlobal(0, 0); 1391 | } 1392 | 1393 | XToolkit.awtLock(); 1394 | try { 1395 | Object wpeer = XToolkit.targetToPeer(comp); 1396 | if (wpeer == null 1397 | || !(wpeer instanceof XDecoratedPeer) 1398 | || ((XDecoratedPeer)wpeer).configure_seen) 1399 | { 1400 | return toGlobal(0, 0); 1401 | } 1402 | 1403 | // wpeer is an XDecoratedPeer not yet fully adopted by WM 1404 | Point pt = toOtherWindow(getContentWindow(), 1405 | ((XDecoratedPeer)wpeer).getContentWindow(), 1406 | 0, 0); 1407 | 1408 | if (pt == null) { 1409 | pt = new Point(((XBaseWindow)wpeer).getAbsoluteX(), ((XBaseWindow)wpeer).getAbsoluteY()); 1410 | } 1411 | pt.x += comp.getX(); 1412 | pt.y += comp.getY(); 1413 | return pt; 1414 | } finally { 1415 | XToolkit.awtUnlock(); 1416 | } 1417 | } 1418 | } 1419 | 1420 | 1421 | static void setBData(KeyEvent e, byte[] data) { 1422 | AWTAccessor.getAWTEventAccessor().setBData(e, data); 1423 | } 1424 | 1425 | public void postKeyEvent(int id, long when, int keyCode, int keyChar, 1426 | int keyLocation, int state, long event, int eventSize, long rawCode, 1427 | int unicodeFromPrimaryKeysym, int extendedKeyCode) 1428 | 1429 | { 1430 | long jWhen = XToolkit.nowMillisUTC_offset(when); 1431 | int modifiers = getModifiers(state, 0, keyCode); 1432 | 1433 | KeyEvent ke = new KeyEvent((Component)getEventSource(), id, jWhen, 1434 | modifiers, keyCode, (char)keyChar, keyLocation); 1435 | if (event != 0) { 1436 | byte[] data = Native.toBytes(event, eventSize); 1437 | setBData(ke, data); 1438 | } 1439 | 1440 | AWTAccessor.KeyEventAccessor kea = AWTAccessor.getKeyEventAccessor(); 1441 | kea.setRawCode(ke, rawCode); 1442 | kea.setPrimaryLevelUnicode(ke, (long)unicodeFromPrimaryKeysym); 1443 | kea.setExtendedKeyCode(ke, (long)extendedKeyCode); 1444 | postEventToEventQueue(ke); 1445 | } 1446 | 1447 | static native int getAWTKeyCodeForKeySym(int keysym); 1448 | static native int getKeySymForAWTKeyCode(int keycode); 1449 | 1450 | /* These two methods are actually applicable to toplevel windows only. 1451 | * However, the functionality is required by both the XWindowPeer and 1452 | * XWarningWindow, both of which have the XWindow as a common ancestor. 1453 | * See XWM.setMotifDecor() for details. 1454 | */ 1455 | public PropMwmHints getMWMHints() { 1456 | if (mwm_hints == null) { 1457 | mwm_hints = new PropMwmHints(); 1458 | if (!XWM.XA_MWM_HINTS.getAtomData(getWindow(), mwm_hints.pData, MWMConstants.PROP_MWM_HINTS_ELEMENTS)) { 1459 | mwm_hints.zero(); 1460 | } 1461 | } 1462 | return mwm_hints; 1463 | } 1464 | 1465 | public void setMWMHints(PropMwmHints hints) { 1466 | mwm_hints = hints; 1467 | if (hints != null) { 1468 | XWM.XA_MWM_HINTS.setAtomData(getWindow(), mwm_hints.pData, MWMConstants.PROP_MWM_HINTS_ELEMENTS); 1469 | } 1470 | } 1471 | 1472 | protected final void initWMProtocols() { 1473 | wm_protocols.setAtomListProperty(this, getWMProtocols()); 1474 | } 1475 | 1476 | /** 1477 | * Returns list of protocols which should be installed on this window. 1478 | * Descendants can override this method to add class-specific protocols 1479 | */ 1480 | protected XAtomList getWMProtocols() { 1481 | // No protocols on simple window 1482 | return new XAtomList(); 1483 | } 1484 | 1485 | /** 1486 | * Indicates if the window is currently in the FSEM. 1487 | * Synchronization: state lock. 1488 | */ 1489 | private boolean fullScreenExclusiveModeState = false; 1490 | 1491 | // Implementation of the X11ComponentPeer 1492 | @Override 1493 | public void setFullScreenExclusiveModeState(boolean state) { 1494 | synchronized (getStateLock()) { 1495 | fullScreenExclusiveModeState = state; 1496 | } 1497 | } 1498 | 1499 | public final boolean isFullScreenExclusiveMode() { 1500 | synchronized (getStateLock()) { 1501 | return fullScreenExclusiveModeState; 1502 | } 1503 | } 1504 | 1505 | } -------------------------------------------------------------------------------- /build/LinuxJavaFixes-1.0.0-SNAPSHOT.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Paullo612/LinuxJavaFixes/976cbb74166e7c1ad562f401021af7a1667997d3/build/LinuxJavaFixes-1.0.0-SNAPSHOT.jar -------------------------------------------------------------------------------- /build/javassist-3.25.0-GA.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Paullo612/LinuxJavaFixes/976cbb74166e7c1ad562f401021af7a1667997d3/build/javassist-3.25.0-GA.jar -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | ru.mate.java.patch 8 | LinuxJavaFixes 9 | jar 10 | 1.0.0-SNAPSHOT 11 | 12 | 13 | org.javassist 14 | javassist 15 | 3.25.0-GA 16 | 17 | 18 | 19 | 20 | 21 | org.apache.maven.plugins 22 | maven-compiler-plugin 23 | 24 | 1.6 25 | 1.6 26 | UTF-8 27 | 28 | 29 | 30 | org.apache.maven.plugins 31 | maven-jar-plugin 32 | 33 | 34 | src/main/resources/META-INF/MANIFEST.MF 35 | 36 | 37 | 38 | 39 | org.apache.maven.plugins 40 | maven-dependency-plugin 41 | 42 | 43 | copy-dependencies 44 | package 45 | 46 | copy-dependencies 47 | 48 | 49 | runtime 50 | 51 | ${project.build.directory} 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /src/main/java/KeyEventDemo.java: -------------------------------------------------------------------------------- 1 | /* 2 | * KeyEventDemo 3 | * java -Djava.endorsed.dirs=target KeyEventDemo 4 | * java -javaagent:target/LinuxJavaFixes-1.0.0-SNAPSHOT.jar=print KeyEventDemo 5 | */ 6 | 7 | import java.awt.BorderLayout; 8 | import java.awt.Dimension; 9 | import java.awt.event.*; 10 | import javax.swing.*; 11 | 12 | public class KeyEventDemo extends JFrame 13 | implements KeyListener, 14 | ActionListener 15 | { 16 | JTextArea displayArea; 17 | JTextField typingArea; 18 | static final String newline = System.getProperty("line.separator"); 19 | 20 | public static void main(String[] args) { 21 | /* Use an appropriate Look and Feel */ 22 | try { 23 | //UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowsLookAndFeel"); 24 | //UIManager.setLookAndFeel("com.sun.java.swing.plaf.gtk.GTKLookAndFeel"); 25 | UIManager.setLookAndFeel("javax.swing.plaf.metal.MetalLookAndFeel"); 26 | } catch (UnsupportedLookAndFeelException ex) { 27 | ex.printStackTrace(); 28 | } catch (IllegalAccessException ex) { 29 | ex.printStackTrace(); 30 | } catch (InstantiationException ex) { 31 | ex.printStackTrace(); 32 | } catch (ClassNotFoundException ex) { 33 | ex.printStackTrace(); 34 | } 35 | /* Turn off metal's use of bold fonts */ 36 | UIManager.put("swing.boldMetal", Boolean.FALSE); 37 | 38 | //Schedule a job for event dispatch thread: 39 | //creating and showing this application's GUI. 40 | javax.swing.SwingUtilities.invokeLater(new Runnable() { 41 | public void run() { 42 | createAndShowGUI(); 43 | } 44 | }); 45 | } 46 | 47 | /** 48 | * Create the GUI and show it. For thread safety, 49 | * this method should be invoked from the 50 | * event-dispatching thread. 51 | */ 52 | private static void createAndShowGUI() { 53 | //Create and set up the window. 54 | KeyEventDemo frame = new KeyEventDemo("KeyEventDemo"); 55 | frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 56 | 57 | //Set up the content pane. 58 | frame.addComponentsToPane(); 59 | 60 | 61 | //Display the window. 62 | frame.pack(); 63 | frame.setVisible(true); 64 | } 65 | 66 | private void addComponentsToPane() { 67 | 68 | JButton button = new JButton("Clear"); 69 | button.addActionListener(this); 70 | 71 | typingArea = new JTextField(20); 72 | typingArea.addKeyListener(this); 73 | 74 | //Uncomment this if you wish to turn off focus 75 | //traversal. The focus subsystem consumes 76 | //focus traversal keys, such as Tab and Shift Tab. 77 | //If you uncomment the following line of code, this 78 | //disables focus traversal and the Tab events will 79 | //become available to the key event listener. 80 | //typingArea.setFocusTraversalKeysEnabled(false); 81 | 82 | displayArea = new JTextArea(); 83 | displayArea.setEditable(false); 84 | JScrollPane scrollPane = new JScrollPane(displayArea); 85 | scrollPane.setPreferredSize(new Dimension(375, 125)); 86 | 87 | getContentPane().add(typingArea, BorderLayout.PAGE_START); 88 | getContentPane().add(scrollPane, BorderLayout.CENTER); 89 | getContentPane().add(button, BorderLayout.PAGE_END); 90 | } 91 | 92 | public KeyEventDemo(String name) { 93 | super(name); 94 | } 95 | 96 | 97 | /** Handle the key typed event from the text field. */ 98 | public void keyTyped(KeyEvent e) { 99 | displayInfo(e, "KEY TYPED: "); 100 | } 101 | 102 | /** Handle the key pressed event from the text field. */ 103 | public void keyPressed(KeyEvent e) { 104 | displayInfo(e, "KEY PRESSED: "); 105 | } 106 | 107 | /** Handle the key released event from the text field. */ 108 | public void keyReleased(KeyEvent e) { 109 | displayInfo(e, "KEY RELEASED: "); 110 | } 111 | 112 | /** Handle the button click. */ 113 | public void actionPerformed(ActionEvent e) { 114 | //Clear the text components. 115 | displayArea.setText(""); 116 | typingArea.setText(""); 117 | 118 | //Return the focus to the typing area. 119 | typingArea.requestFocusInWindow(); 120 | } 121 | 122 | /* 123 | * We have to jump through some hoops to avoid 124 | * trying to print non-printing characters 125 | * such as Shift. (Not only do they not print, 126 | * but if you put them in a String, the characters 127 | * afterward won't show up in the text area.) 128 | */ 129 | private void displayInfo(KeyEvent e, String keyStatus){ 130 | //XKeysym 131 | 132 | 133 | //You should only rely on the key char if the event 134 | //is a key typed event. 135 | int id = e.getID(); 136 | String keyString; 137 | if (id == KeyEvent.KEY_TYPED) { 138 | char c = e.getKeyChar(); 139 | int keyCode = e.getKeyCode(); 140 | keyString = "key character = '" + c + "'"+ 141 | " key code = " + keyCode 142 | + " (" 143 | + KeyEvent.getKeyText(keyCode) 144 | + ") "+e.getExtendedKeyCode();; 145 | 146 | } else { 147 | int keyCode = e.getKeyCode(); 148 | keyString = "key code = " + keyCode 149 | + " (" 150 | + KeyEvent.getKeyText(keyCode) 151 | + ") "+e.getExtendedKeyCode(); 152 | } 153 | 154 | int modifiersEx = e.getModifiersEx(); 155 | String modString = "extended modifiers = " + modifiersEx; 156 | String tmpString = KeyEvent.getModifiersExText(modifiersEx); 157 | if (tmpString.length() > 0) { 158 | modString += " (" + tmpString + ")"; 159 | } else { 160 | modString += " (no extended modifiers)"; 161 | } 162 | 163 | String actionString = "action key? "; 164 | if (e.isActionKey()) { 165 | actionString += "YES"; 166 | } else { 167 | actionString += "NO"; 168 | } 169 | 170 | String locationString = "key location: "; 171 | int location = e.getKeyLocation(); 172 | if (location == KeyEvent.KEY_LOCATION_STANDARD) { 173 | locationString += "standard"; 174 | } else if (location == KeyEvent.KEY_LOCATION_LEFT) { 175 | locationString += "left"; 176 | } else if (location == KeyEvent.KEY_LOCATION_RIGHT) { 177 | locationString += "right"; 178 | } else if (location == KeyEvent.KEY_LOCATION_NUMPAD) { 179 | locationString += "numpad"; 180 | } else { // (location == KeyEvent.KEY_LOCATION_UNKNOWN) 181 | locationString += "unknown"; 182 | } 183 | 184 | System.out.println(keyStatus + newline 185 | + " " + keyString + newline 186 | + " " + modString + newline 187 | + " " + actionString + newline 188 | + " " + locationString + newline); 189 | 190 | displayArea.append(keyStatus + newline 191 | + " " + keyString + newline 192 | + " " + modString + newline 193 | + " " + actionString + newline 194 | + " " + locationString + newline); 195 | displayArea.setCaretPosition(displayArea.getDocument().getLength()); 196 | } 197 | } -------------------------------------------------------------------------------- /src/main/java/net/x11/patch/LinuxJavaPatchAgent.java: -------------------------------------------------------------------------------- 1 | package net.x11.patch; 2 | 3 | import java.io.*; 4 | import java.lang.instrument.Instrumentation; 5 | import java.util.Properties; 6 | 7 | 8 | /** 9 | * Created with IntelliJ IDEA. 10 | * User: mzheludkov 11 | * -javaagent:target/LinuxJavaFixes-1.0.0-SNAPSHOT.jar 12 | */ 13 | public class LinuxJavaPatchAgent { 14 | 15 | public static void premain(String agentArgument, Instrumentation instrumentation) { 16 | if(agentArgument==null || !agentArgument.startsWith(SWTEventTableTransformer.SWT)) { 17 | instrumentation.addTransformer(new XKeysymTransformer(agentArgument)); 18 | } else if(agentArgument.startsWith(SWTEventTableTransformer.SWT)){ 19 | instrumentation.addTransformer(new SWTEventTableTransformer(agentArgument)); 20 | } 21 | } 22 | 23 | public static Properties getProperties(String defaultProperties, String propertiesFile) throws IOException { 24 | Properties props = null; 25 | if(propertiesFile !=null && !propertiesFile.equals("")) { 26 | File mapping = new File(propertiesFile); 27 | if(mapping.exists()) { 28 | props = new Properties(); 29 | System.out.println("LinuxJavaPatchAgent.loaded properties from "+mapping); 30 | props.load(new FileInputStream(mapping)); 31 | 32 | } 33 | } 34 | if(props==null) { 35 | props = new Properties(); 36 | System.out.println("LinuxJavaPatchAgent.loaded properties from classpath! "+defaultProperties); 37 | props.load(LinuxJavaPatchAgent.class.getClassLoader().getResourceAsStream(defaultProperties)); 38 | } 39 | return props; 40 | } 41 | 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/net/x11/patch/SWTEventTableTransformer.java: -------------------------------------------------------------------------------- 1 | package net.x11.patch; 2 | 3 | import javassist.*; 4 | 5 | import java.lang.instrument.ClassFileTransformer; 6 | import java.lang.instrument.IllegalClassFormatException; 7 | import java.net.URL; 8 | import java.net.URLClassLoader; 9 | import java.security.ProtectionDomain; 10 | import java.util.Properties; 11 | 12 | /** 13 | * Created by mikl on 08.02.14. 14 | */ 15 | public class SWTEventTableTransformer implements ClassFileTransformer { 16 | public static final String EVENT_TABLE_CLASS = "org/eclipse/swt/widgets/EventTable"; 17 | public static final String PATCH_KEY_MAPPING_PROPERTIES = "swt.key.mapping.properties"; 18 | public static final String PRINT = "print"; 19 | public static final String SWT = "swt"; 20 | private String agentArgument; 21 | 22 | public SWTEventTableTransformer(String agentArgument) { 23 | this.agentArgument = agentArgument==null||agentArgument.equalsIgnoreCase(SWT) ? null : agentArgument.substring(4); 24 | } 25 | 26 | @Override 27 | public byte[] transform(ClassLoader loader, String className, Class classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { 28 | try { 29 | return className.equals(EVENT_TABLE_CLASS) ? doClass(loader, className, classBeingRedefined, classfileBuffer) : classfileBuffer; 30 | } catch (Throwable err) { 31 | err.printStackTrace(); 32 | return classfileBuffer; 33 | } 34 | } 35 | 36 | private byte[] doClass(ClassLoader classLoader, String name, Class clazz, byte[] b) throws ClassNotFoundException, NotFoundException { 37 | CtClass cl = null; 38 | ClassPool pool = ClassPool.getDefault(); 39 | pool.appendClassPath(new LoaderClassPath(classLoader)); 40 | try { 41 | cl = pool.makeClass(new java.io.ByteArrayInputStream(b)); 42 | CtMethod method = cl.getDeclaredMethod("sendEvent"); 43 | if(agentArgument!=null && agentArgument.equals(PRINT)) { 44 | method.insertBefore("if($1.type==1) {System.out.println(\"keyCode=\"+$1.keyCode);};" ); 45 | } else { 46 | Properties props = LinuxJavaPatchAgent.getProperties(PATCH_KEY_MAPPING_PROPERTIES, agentArgument); 47 | for(Object key: props.keySet()) { 48 | String value = props.getProperty((String) key); 49 | method.insertBefore("if(($1.type==1) && $1.keyCode==" + key + " && ((event.stateMask & org.eclipse.swt.SWT.MOD1) !=0 || (event.stateMask & org.eclipse.swt.SWT.MOD2) !=0 )) { $1.keyCode=" + value + ";}"); 50 | } 51 | } 52 | b = cl.toBytecode(); 53 | } catch (Exception e) { 54 | e.printStackTrace(System.out); 55 | System.out.println("LinuxJavaPatchAgent.Could not instrument " + name + ", exception : " + e.getMessage()); 56 | } finally { 57 | if (cl != null) { 58 | cl.detach(); 59 | } 60 | } 61 | return b; 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/net/x11/patch/XKeysymTransformer.java: -------------------------------------------------------------------------------- 1 | package net.x11.patch; 2 | 3 | import java.lang.instrument.ClassFileTransformer; 4 | import java.lang.instrument.IllegalClassFormatException; 5 | import java.security.ProtectionDomain; 6 | import java.util.Hashtable; 7 | import java.util.Properties; 8 | 9 | import javassist.ClassClassPath; 10 | import javassist.ClassPool; 11 | import javassist.CtClass; 12 | import javassist.CtConstructor; 13 | import javassist.CtMethod; 14 | import javassist.LoaderClassPath; 15 | 16 | /** 17 | * Created by mikl on 08.02.14. 18 | */ 19 | public class XKeysymTransformer implements ClassFileTransformer { 20 | public static final String XNET_PROTOCOL = "sun/awt/X11/XKeysym"; 21 | public static final String PATCH_KEY_MAPPING_PROPERTIES = "patch.key.mapping.properties"; 22 | public static final String PRINT = "print"; 23 | private String agentArgument; 24 | 25 | public XKeysymTransformer(String agentArgument) { 26 | this.agentArgument = agentArgument; 27 | } 28 | 29 | @Override 30 | public byte[] transform(ClassLoader loader, String className, Class classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { 31 | try { 32 | return className!=null && className.equals(XNET_PROTOCOL) ? doClass(loader, className, classBeingRedefined, classfileBuffer) : classfileBuffer; 33 | } catch (Throwable err) { 34 | err.printStackTrace(); 35 | return classfileBuffer; 36 | } 37 | } 38 | 39 | private byte[] doClass(ClassLoader loader, String name, Class clazz, byte[] b) { 40 | CtClass cl = null; 41 | ClassPool pool = new ClassPool(); 42 | pool.appendClassPath(new LoaderClassPath(loader)); 43 | pool.appendClassPath(new ClassClassPath(Hashtable.class)); 44 | try { 45 | pool.appendClassPath(new ClassClassPath(Class.forName("sun.awt.X11.XKeysym$Keysym2JavaKeycode"))); 46 | } catch (ClassNotFoundException e) { 47 | e.printStackTrace(); 48 | } 49 | try { 50 | cl = pool.makeClass(new java.io.ByteArrayInputStream(b)); 51 | if(agentArgument!=null && agentArgument.equals(PRINT)) { 52 | CtMethod method = cl.getDeclaredMethod("getJavaKeycode"); 53 | method.insertBefore("System.out.println(\"LinuxJavaPatchAgent.keysym=\"+Long.toHexString($1));"); 54 | } else { 55 | Properties props = LinuxJavaPatchAgent.getProperties(PATCH_KEY_MAPPING_PROPERTIES, agentArgument); 56 | CtConstructor classInitializer = cl.makeClassInitializer(); 57 | 58 | for(Object key: props.keySet()) { 59 | String value = props.getProperty((String) key); 60 | classInitializer.insertAfter("keysym2JavaKeycodeHash.put( Long.valueOf(0x"+key+"l), new sun.awt.X11.XKeysym$Keysym2JavaKeycode(java.awt.event.KeyEvent.VK_"+value+", java.awt.event.KeyEvent.KEY_LOCATION_STANDARD));"); 61 | } 62 | } 63 | b = cl.toBytecode(); 64 | } catch (Exception e) { 65 | e.printStackTrace(); 66 | System.out.println("LinuxJavaPatchAgent.Could not instrument " + name + ", exception : " + e.getMessage()); 67 | } finally { 68 | if (cl != null) { 69 | cl.detach(); 70 | } 71 | } 72 | return b; 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/MANIFEST.MF: -------------------------------------------------------------------------------- 1 | Premain-Class: net.x11.patch.LinuxJavaPatchAgent 2 | Boot-Class-Path: javassist-3.25.0-GA.jar 3 | -------------------------------------------------------------------------------- /src/main/resources/patch.key.mapping.properties: -------------------------------------------------------------------------------- 1 | 6ca=Q 2 | 6c3=W 3 | 6d5=E 4 | 6cb=R 5 | 6c5=T 6 | 6ce=Y 7 | 6c7=U 8 | 6db=I 9 | 6dd=O 10 | 6da=P 11 | 6c6=A 12 | 6d9=S 13 | 6d7=D 14 | 6c1=F 15 | 6d0=G 16 | 6d2=H 17 | 6cf=J 18 | 6cc=K 19 | 6c4=L 20 | 6d1=Z 21 | 6de=X 22 | 6d3=C 23 | 6cd=V 24 | 6c9=B 25 | 6d4=N 26 | 6d8=M 27 | 6c8=OPEN_BRACKET 28 | 6df=CLOSE_BRACKET 29 | 6d6=SEMICOLON 30 | 6dc=QUOTE 31 | 6c2=COMMA 32 | 6c0=PERIOD 33 | 6a3=BACK_QUOTE 34 | -------------------------------------------------------------------------------- /src/main/resources/swt.key.mapping.properties: -------------------------------------------------------------------------------- 1 | 1081=113 2 | 1094=119 3 | 1091=101 4 | 1082=114 5 | 1077=116 6 | 1085=121 7 | 1075=117 8 | 1096=105 9 | 1097=111 10 | 1079=112 11 | 12 | 1092=97 13 | 1099=115 14 | 1074=100 15 | 1072=102 16 | 1087=103 17 | 1088=104 18 | 1086=106 19 | 1083=107 20 | 1076=108 21 | 22 | 1103=122 23 | 1095=120 24 | 1089=99 25 | 1084=118 26 | 1080=98 27 | 1090=110 28 | 1100=109 --------------------------------------------------------------------------------