// gemX_input.cc update to support certain characters under X /* ************************************************************************ * The Amulet User Interface Development Environment * * ************************************************************************ * This code was written as part of the Amulet project at * * Carnegie Mellon University, and has been placed in the public * * domain. If you are using this code or any part of Amulet, * * please contact amulet@cs.cmu.edu to be put on the mailing list. * * ************************************************************************/ /* This file contains member function definitions for the Am_Drawonable_Impl object primarily concerned with interaction. The followed changes were made to version 3 as released by Stuart Pook: The first change is very simple; it allows the top half (non-ASCII part) of the isolatin1 character set to be entered from the keyboard. The second and third patches modify your code to use XLookupString which is (according to O'Reilly) the correct way to translate keyboard events into characters. I need this correction so that my keyboard modifications (made with xmodmap) work. Note that the keyboard modifications create a third keyboard modifier and that this is not recognised by XKeycodeToKeysym. The following lines are part of the keyboard modifications that I make with xmodmap. Note that this way I am able to get four characters (the lower and upper case versions of two letters) onto each function key that I hijack. keysym F10 = egrave NoSymbol ecircumflex keysym F11 = agrave NoSymbol acircumflex keysym F12 = Mode_switch clear mod3 add mod3 = Mode_switch */ extern "C" { #include #include #include #include #include #include } #include #include #include GEM__H #include UNIV_MAP__H #include "gemX_time.h" #include GEMX__H extern Screen_Manager Scrn_Mgr; bool Am_Main_Loop_Go = true; // An array of XEvent names used for debugging. If you have an // XEvent variable named event_return, do event_names[event_return.type] to // print the name of it's XEvent type. // Taken from Xlib Programming Manual Vol. One, p. 256-57 // static char *event_names[] = { "", "", "KeyPress", "KeyRelease", "ButtonPress", "ButtonRelease", "MotionNotify", "EnterNotify", "LeaveNotify", "FocusIn", "FocusOut", "KeymapNotify", "Expose", "GraphicsExpose", "NoExpose", "VisibilityNotify", "CreateNotify", "DestroyNotify", "UnmapNotify", "MapNotify", "MapRequest", "ReparentNotify", "ConfigureNotify", "ConfigureRequest", "GravityNotify", "ResizeRequest", "CirculateNotify", "CirculateRequest", "PropertyNotify", "SelectionClear", "SelectionRequest", "SelectionNotify", "ColormapNotify", "ClientMessage", "MappingNotify", }; Bool is_mapnotify(Display * /* dpy */, XEvent *event_return, XPointer xlib_window) { switch (event_return->type) { case MapNotify: // Got MapNotify event, but if multiple windows have been created and // Process_Event has not yet been called, then old MapNotify events may // still be in the queue. Check that we get the one for our window. if (event_return->xmap.window == *(Window*)xlib_window) { return True; } break; default: break; } return False; } // // // // // // // // // // // // // // // // // // // // // Convert an X input event into a Am_Input_Char // // // // // // // // // // // // // // // // // // // // int Am_Double_Click_Time = 250; // in milleseconds Am_Click_Count Check_Multi_Click(int code, unsigned int state, Am_Button_Down down, Time time, Screen_Desc* screen) { Am_Click_Count result = Am_SINGLE_CLICK; if (Am_Double_Click_Time) { // else not interested in multi-click // if a down press, then check if double click. If up, then use current // down count. If other mouse event, then ignore multi-click if ( down == Am_NEITHER) ; // result is OK, do nothing else if ( down == Am_BUTTON_UP) { // use current value if (screen->click_counter>= 7) result = Am_MANY_CLICK; else result = (Am_Click_Count) (screen->click_counter + 1); // otherwise, just use single click, so result OK } else { // is a down press if ( (code == screen->last_click_code) && (state == screen->last_click_state) && ( time <= screen->last_click_time + Am_Double_Click_Time)){ // is multi-click ++(screen->click_counter); if (screen->click_counter>= 7) result = Am_MANY_CLICK; else result = (Am_Click_Count) (screen->click_counter + 1); } else screen->click_counter = 0; // in either case, set up variables for next time screen->last_click_code = code; screen->last_click_state = state; screen->last_click_time = time; } } return result; } Am_Input_Char create_input_char_from_code (short code, unsigned int state, Am_Button_Down down, Am_Click_Count mouse) { bool shft = false; bool ctrl = false; bool meta = false; if ((state & ShiftMask)) shft = true; //only use shift lock for alphabetic characters if ((state & LockMask) && (code>= 'a') && (code <= 'z')) shft = true; if (state & ControlMask) ctrl = true; if (state & Mod1Mask) meta = true; Am_Input_Char ic = Am_Input_Char(code, shft, ctrl, meta, down, mouse); return ic; } Am_IMPL_MAP(int2int, int, 0, int, 0) static Am_Map_int2int *am_key_map = 0; // returns character code or 0 if modifier or -1 if illegal short Map_Sym_To_Code(KeySym sym) { short c; if (am_key_map == 0) Am_Init_Key_Map(); c = (short)(am_key_map->GetAt((int) sym)); if (c) return c; if ( (sym>= 65505) && (sym <= 65518) ) { // symbol is a modifier key c = 0; } else { // unknown character c = -1; } return c; } void set_am_key_map (int Sym, int Code) { am_key_map->SetAt(Sym, Code); } #if XlibSpecificationRelease < 6 // define XK_* constants that are not defined in keysymdefs.h prior to X11R6 // these values are consistent with those used in Amulet V2 #define XK_Page_Up 0xFF55 #define XK_Page_Down 0xFF56 #define XK_KP_Home 0xFF95 #define XK_KP_Left 0xFF96 #define XK_KP_Up 0xFF97 #define XK_KP_Right 0xFF98 #define XK_KP_Down 0xFF99 #define XK_KP_Prior 0xFF9A #define XK_KP_Page_Up 0xFF9A #define XK_KP_Next 0xFF9B #define XK_KP_Page_Down 0xFF9B #define XK_KP_End 0xFF9C #define XK_KP_Begin 0xFF9D #define XK_KP_Insert 0xFF9E #define XK_KP_Delete 0xFF9F #endif void Am_Init_Key_Map() { // The commented out lines have no Amulet equivalent. Check there first! // Look for these XK_* constants in include/X11/keysymdefs.h if (am_key_map == 0) am_key_map = new Am_Map_int2int(153); set_am_key_map(XK_BackSpace, Am_BACKSPACE); set_am_key_map(XK_Tab, Am_TAB); set_am_key_map(XK_Linefeed, Am_LINEFEED); set_am_key_map(XK_Clear, Am_CLEAR); set_am_key_map(XK_Return, Am_RETURN); set_am_key_map(XK_Pause, Am_BREAK); set_am_key_map(XK_Scroll_Lock, Am_SCROLL_LOCK); set_am_key_map(XK_Mode_switch, Am_ALT_GRAPH); // set_am_key_map(XK_Sys_Req, Am_SYS_REQ); set_am_key_map(XK_Escape, Am_ESC); set_am_key_map(XK_Delete, Am_DELETE); set_am_key_map (XK_Multi_key, Am_COMPOSE_CHARACTER); set_am_key_map(XK_Home, Am_HOME); set_am_key_map(XK_Left, Am_LEFT_ARROW); set_am_key_map(XK_Up, Am_UP_ARROW); set_am_key_map(XK_Right, Am_RIGHT_ARROW); set_am_key_map(XK_Down, Am_DOWN_ARROW); set_am_key_map(XK_Prior, Am_PRIOR); set_am_key_map(XK_Page_Up, Am_PAGE_UP); set_am_key_map(XK_Next, Am_NEXT); set_am_key_map(XK_Page_Down, Am_PAGE_DOWN); set_am_key_map(XK_End, Am_END); // set_am_key_map(XK_Begin, Am_BEGIN); set_am_key_map(XK_Select, Am_SELECT); set_am_key_map(XK_Print, Am_PRINT_KEY); // set_am_key_map(XK_Execute, Am_EXECUTE); set_am_key_map(XK_Insert, Am_INSERT); set_am_key_map(XK_Undo, Am_UNDO_KEY); set_am_key_map(XK_Redo, Am_REDO_KEY); set_am_key_map(XK_Menu, Am_MENU); set_am_key_map(XK_Find, Am_FIND); set_am_key_map(XK_Cancel, Am_CANCEL); set_am_key_map(XK_Help, Am_HELP); set_am_key_map(XK_Break, Am_BREAK); // set_am_key_map(XK_Mode_switch, ); // set_am_key_map(XK_script_switch, ); set_am_key_map(XK_Num_Lock, Am_NUMLOCK); set_am_key_map(XK_KP_Space, Am_SPACE); set_am_key_map(XK_KP_Tab, Am_TAB); set_am_key_map(XK_KP_Enter, Am_ENTER); set_am_key_map(XK_KP_F1, Am_PF1); set_am_key_map(XK_KP_F2, Am_PF2); set_am_key_map(XK_KP_F3, Am_PF3); set_am_key_map(XK_KP_F4, Am_PF4); set_am_key_map(XK_KP_Begin, Am_R11); set_am_key_map(XK_KP_Home, Am_HOME); set_am_key_map(XK_KP_Left, Am_LEFT_ARROW); set_am_key_map(XK_KP_Up, Am_UP_ARROW); set_am_key_map(XK_KP_Right, Am_RIGHT_ARROW); set_am_key_map(XK_KP_Down, Am_DOWN_ARROW); set_am_key_map(XK_KP_Prior, Am_PRIOR); set_am_key_map(XK_KP_Page_Up, Am_PAGE_UP); set_am_key_map(XK_KP_Next, Am_NEXT); set_am_key_map(XK_KP_Page_Down, Am_PAGE_DOWN); set_am_key_map(XK_KP_End, Am_END); // set_am_key_map(XK_KP_Begin, Am_BEGIN); set_am_key_map(XK_KP_Insert, Am_INSERT); set_am_key_map(XK_KP_Delete, Am_DELETE_CHAR); set_am_key_map(XK_KP_Equal, (short)'='); set_am_key_map(XK_KP_Multiply, (short)'*'); set_am_key_map(XK_KP_Add, (short)'+'); set_am_key_map(XK_KP_Separator, (short)','); set_am_key_map(XK_KP_Subtract, (short)'-'); set_am_key_map(XK_KP_Decimal, (short)'.'); set_am_key_map(XK_KP_Divide, (short)'/'); set_am_key_map(XK_KP_0, (short)'0'); set_am_key_map(XK_KP_1, (short)'1'); set_am_key_map(XK_KP_2, (short)'2'); set_am_key_map(XK_KP_3, (short)'3'); set_am_key_map(XK_KP_4, (short)'4'); set_am_key_map(XK_KP_5, (short)'5'); set_am_key_map(XK_KP_6, (short)'6'); set_am_key_map(XK_KP_7, (short)'7'); set_am_key_map(XK_KP_8, (short)'8'); set_am_key_map(XK_KP_9, (short)'9'); set_am_key_map(XK_F1, Am_F1); set_am_key_map(XK_F2, Am_F2); set_am_key_map(XK_F3, Am_F3); set_am_key_map(XK_F4, Am_F4); set_am_key_map(XK_F5, Am_F5); set_am_key_map(XK_F6, Am_F6); set_am_key_map(XK_F7, Am_F7); set_am_key_map(XK_F8, Am_F8); set_am_key_map(XK_F9, Am_F9); set_am_key_map(XK_F10, Am_F10); set_am_key_map(XK_L1, Am_L1); set_am_key_map(XK_L2, Am_L2); set_am_key_map(XK_L3, Am_L3); set_am_key_map(XK_L4, Am_L4); set_am_key_map(XK_L5, Am_L5); set_am_key_map(XK_L6, Am_L6); set_am_key_map(XK_L7, Am_L7); set_am_key_map(XK_L8, Am_L8); set_am_key_map(XK_L9, Am_L9); set_am_key_map(XK_L10, Am_L10); set_am_key_map(XK_R1, Am_R1); set_am_key_map(XK_R2, Am_R2); set_am_key_map(XK_R3, Am_R3); set_am_key_map(XK_R4, Am_R4); set_am_key_map(XK_R5, Am_R5); set_am_key_map(XK_R6, Am_R6); set_am_key_map(XK_R7, Am_R7); set_am_key_map(XK_R8, Am_R8); set_am_key_map(XK_R9, Am_R9); set_am_key_map(XK_R10, Am_R10); set_am_key_map(XK_R11, Am_R11); set_am_key_map(XK_R12, Am_R12); set_am_key_map(XK_R13, Am_R13); set_am_key_map(XK_R14, Am_R14); set_am_key_map(XK_R15, Am_R15); #ifdef XK_LATIN1 // this should always be defined, but make sure just in case set_am_key_map(XK_space, Am_SPACE); set_am_key_map(XK_exclam, (short)'!'); set_am_key_map(XK_quotedbl, (short)'\"'); set_am_key_map(XK_numbersign, (short)'#'); set_am_key_map(XK_dollar, (short)'$'); set_am_key_map(XK_percent, (short)'%'); set_am_key_map(XK_ampersand, (short)'&'); set_am_key_map(XK_apostrophe, (short)'\''); // set_am_key_map(XK_quoteright same as apostrophe set_am_key_map(XK_parenleft, (short)'('); set_am_key_map(XK_parenright, (short)')'); set_am_key_map(XK_asterisk, (short)'*'); set_am_key_map(XK_plus, (short)'+'); set_am_key_map(XK_comma, (short)','); set_am_key_map(XK_minus, (short)'-'); set_am_key_map(XK_period, (short)'.'); set_am_key_map(XK_slash, (short)'/'); set_am_key_map(XK_0, (short)'0'); set_am_key_map(XK_1, (short)'1'); set_am_key_map(XK_2, (short)'2'); set_am_key_map(XK_3, (short)'3'); set_am_key_map(XK_4, (short)'4'); set_am_key_map(XK_5, (short)'5'); set_am_key_map(XK_6, (short)'6'); set_am_key_map(XK_7, (short)'7'); set_am_key_map(XK_8, (short)'8'); set_am_key_map(XK_9, (short)'9'); set_am_key_map(XK_colon, (short)':'); set_am_key_map(XK_semicolon, (short)';'); set_am_key_map(XK_less, (short)'<'); set_am_key_map(XK_equal, (short)'='); set_am_key_map(XK_greater, (short)'>'); set_am_key_map(XK_question, (short)'?'); set_am_key_map(XK_at, (short)'@'); set_am_key_map(XK_A, (short)'A'); set_am_key_map(XK_B, (short)'B'); set_am_key_map(XK_C, (short)'C'); set_am_key_map(XK_D, (short)'D'); set_am_key_map(XK_E, (short)'E'); set_am_key_map(XK_F, (short)'F'); set_am_key_map(XK_G, (short)'G'); set_am_key_map(XK_H, (short)'H'); set_am_key_map(XK_I, (short)'I'); set_am_key_map(XK_J, (short)'J'); set_am_key_map(XK_K, (short)'K'); set_am_key_map(XK_L, (short)'L'); set_am_key_map(XK_M, (short)'M'); set_am_key_map(XK_N, (short)'N'); set_am_key_map(XK_O, (short)'O'); set_am_key_map(XK_P, (short)'P'); set_am_key_map(XK_Q, (short)'Q'); set_am_key_map(XK_R, (short)'R'); set_am_key_map(XK_S, (short)'S'); set_am_key_map(XK_T, (short)'T'); set_am_key_map(XK_U, (short)'U'); set_am_key_map(XK_V, (short)'V'); set_am_key_map(XK_W, (short)'W'); set_am_key_map(XK_X, (short)'X'); set_am_key_map(XK_Y, (short)'Y'); set_am_key_map(XK_Z, (short)'Z'); set_am_key_map(XK_bracketleft, (short)'['); set_am_key_map(XK_backslash, (short)'\\'); set_am_key_map(XK_bracketright, (short)']'); set_am_key_map(XK_asciicircum, (short)'^'); set_am_key_map(XK_underscore, (short)'_'); // set_am_key_map(XK_grave, (short)''); set_am_key_map(XK_quoteleft, (short)'`'); set_am_key_map(XK_a, (short)'a'); set_am_key_map(XK_b, (short)'b'); set_am_key_map(XK_c, (short)'c'); set_am_key_map(XK_d, (short)'d'); set_am_key_map(XK_e, (short)'e'); set_am_key_map(XK_f, (short)'f'); set_am_key_map(XK_g, (short)'g'); set_am_key_map(XK_h, (short)'h'); set_am_key_map(XK_i, (short)'i'); set_am_key_map(XK_j, (short)'j'); set_am_key_map(XK_k, (short)'k'); set_am_key_map(XK_l, (short)'l'); set_am_key_map(XK_m, (short)'m'); set_am_key_map(XK_n, (short)'n'); set_am_key_map(XK_o, (short)'o'); set_am_key_map(XK_p, (short)'p'); set_am_key_map(XK_q, (short)'q'); set_am_key_map(XK_r, (short)'r'); set_am_key_map(XK_s, (short)'s'); set_am_key_map(XK_t, (short)'t'); set_am_key_map(XK_u, (short)'u'); set_am_key_map(XK_v, (short)'v'); set_am_key_map(XK_w, (short)'w'); set_am_key_map(XK_x, (short)'x'); set_am_key_map(XK_y, (short)'y'); set_am_key_map(XK_z, (short)'z'); set_am_key_map(XK_braceleft, (short)'{'); set_am_key_map(XK_bar, (short)'|'); set_am_key_map(XK_braceright, (short)'}'); set_am_key_map(XK_asciitilde, (short)'~'); // since we are doing latin1 let's handle the top half as well set_am_key_map(XK_nobreakspace, 0x0a0); set_am_key_map(XK_exclamdown, 0x0a1); set_am_key_map(XK_cent, 0x0a2); set_am_key_map(XK_sterling, 0x0a3); set_am_key_map(XK_currency, 0x0a4); set_am_key_map(XK_yen, 0x0a5); set_am_key_map(XK_brokenbar, 0x0a6); set_am_key_map(XK_section, 0x0a7); set_am_key_map(XK_diaeresis, 0x0a8); set_am_key_map(XK_copyright, 0x0a9); set_am_key_map(XK_ordfeminine, 0x0aa); set_am_key_map(XK_guillemotleft, 0x0ab); set_am_key_map(XK_notsign, 0x0ac); set_am_key_map(XK_hyphen, 0x0ad); set_am_key_map(XK_registered, 0x0ae); set_am_key_map(XK_macron, 0x0af); set_am_key_map(XK_degree, 0x0b0); set_am_key_map(XK_plusminus, 0x0b1); set_am_key_map(XK_twosuperior, 0x0b2); set_am_key_map(XK_threesuperior, 0x0b3); set_am_key_map(XK_acute, 0x0b4); set_am_key_map(XK_mu, 0x0b5); set_am_key_map(XK_paragraph, 0x0b6); set_am_key_map(XK_periodcentered, 0x0b7); set_am_key_map(XK_cedilla, 0x0b8); set_am_key_map(XK_onesuperior, 0x0b9); set_am_key_map(XK_masculine, 0x0ba); set_am_key_map(XK_guillemotright, 0x0bb); set_am_key_map(XK_onequarter, 0x0bc); set_am_key_map(XK_onehalf, 0x0bd); set_am_key_map(XK_threequarters, 0x0be); set_am_key_map(XK_questiondown, 0x0bf); set_am_key_map(XK_Agrave, 0x0c0); set_am_key_map(XK_Aacute, 0x0c1); set_am_key_map(XK_Acircumflex, 0x0c2); set_am_key_map(XK_Atilde, 0x0c3); set_am_key_map(XK_Adiaeresis, 0x0c4); set_am_key_map(XK_Aring, 0x0c5); set_am_key_map(XK_AE, 0x0c6); set_am_key_map(XK_Ccedilla, 0x0c7); set_am_key_map(XK_Egrave, 0x0c8); set_am_key_map(XK_Eacute, 0x0c9); set_am_key_map(XK_Ecircumflex, 0x0ca); set_am_key_map(XK_Ediaeresis, 0x0cb); set_am_key_map(XK_Igrave, 0x0cc); set_am_key_map(XK_Iacute, 0x0cd); set_am_key_map(XK_Icircumflex, 0x0ce); set_am_key_map(XK_Idiaeresis, 0x0cf); set_am_key_map(XK_ETH, 0x0d0); set_am_key_map(XK_Ntilde, 0x0d1); set_am_key_map(XK_Ograve, 0x0d2); set_am_key_map(XK_Oacute, 0x0d3); set_am_key_map(XK_Ocircumflex, 0x0d4); set_am_key_map(XK_Otilde, 0x0d5); set_am_key_map(XK_Odiaeresis, 0x0d6); set_am_key_map(XK_multiply, 0x0d7); set_am_key_map(XK_Ooblique, 0x0d8); set_am_key_map(XK_Ugrave, 0x0d9); set_am_key_map(XK_Uacute, 0x0da); set_am_key_map(XK_Ucircumflex, 0x0db); set_am_key_map(XK_Udiaeresis, 0x0dc); set_am_key_map(XK_Yacute, 0x0dd); set_am_key_map(XK_THORN, 0x0de); set_am_key_map(XK_ssharp, 0x0df); set_am_key_map(XK_agrave, 0x0e0); set_am_key_map(XK_aacute, 0x0e1); set_am_key_map(XK_acircumflex, 0x0e2); set_am_key_map(XK_atilde, 0x0e3); set_am_key_map(XK_adiaeresis, 0x0e4); set_am_key_map(XK_aring, 0x0e5); set_am_key_map(XK_ae, 0x0e6); set_am_key_map(XK_ccedilla, 0x0e7); set_am_key_map(XK_egrave, 0x0e8); set_am_key_map(XK_eacute, 0x0e9); set_am_key_map(XK_ecircumflex, 0x0ea); set_am_key_map(XK_ediaeresis, 0x0eb); set_am_key_map(XK_igrave, 0x0ec); set_am_key_map(XK_iacute, 0x0ed); set_am_key_map(XK_icircumflex, 0x0ee); set_am_key_map(XK_idiaeresis, 0x0ef); set_am_key_map(XK_eth, 0x0f0); set_am_key_map(XK_ntilde, 0x0f1); set_am_key_map(XK_ograve, 0x0f2); set_am_key_map(XK_oacute, 0x0f3); set_am_key_map(XK_ocircumflex, 0x0f4); set_am_key_map(XK_otilde, 0x0f5); set_am_key_map(XK_odiaeresis, 0x0f6); set_am_key_map(XK_division, 0x0f7); set_am_key_map(XK_oslash, 0x0f8); set_am_key_map(XK_ugrave, 0x0f9); set_am_key_map(XK_uacute, 0x0fa); set_am_key_map(XK_ucircumflex, 0x0fb); set_am_key_map(XK_udiaeresis, 0x0fc); set_am_key_map(XK_yacute, 0x0fd); set_am_key_map(XK_thorn, 0x0fe); #endif // The next section is non-ideal. // The best solution is to find (create?) #defines for these magic // numbers, like the XK_ macros above. // I couldn't find any of these keysyms anywhere. They're all // on Sun keyboards. In the Older Amulet translation, most of these went // to L keys, but that's _not_ correct: you should get the keycode you want, // instead of translating it to L anything. // 5-31-96 af1x set_am_key_map (268500736, Am_REMOVE); set_am_key_map (268500850, Am_INSERT_CHAR); set_am_key_map (268500848, Am_INSERT_LINE); set_am_key_map (268500849, Am_DELETE_LINE); set_am_key_map (268500851, Am_DELETE_CHAR); set_am_key_map (268500847, Am_CLEAR_LINE); set_am_key_map (268500845, Am_USER); set_am_key_map (268828528, Am_PROPS_KEY); set_am_key_map (268828529, Am_FRONT_KEY); set_am_key_map (268828530, Am_COPY_KEY); set_am_key_map (268828531, Am_OPEN_KEY); set_am_key_map (268828532, Am_PASTE_KEY); set_am_key_map (268828533, Am_CUT_KEY); /* // these aren't handled yet: what Am_ consts should we use? case 268828535: c = Am_; break; // SunAudioMute case 268828536: c = Am_; break; // SunAudioLowerVolume case 268828537: c = Am_; break; // SunAudioRaiseVolume case 268828534: c = Am_; break; // SunPowerSwitch */ } Am_Input_Char create_input_char_from_key (XKeyEvent * keyevent) { unsigned int keycode = keyevent->keycode; unsigned int state = keyevent->state; Display * disp = keyevent->display; int index; if (state & ShiftMask) index = 1; else index = 0; KeySym sym; { char buffer[4]; XLookupString(keyevent, buffer, sizeof buffer, &sym, 0); } if ( (sym == NoSymbol) && (index == 1) ) { // try again with unshifted index // This makes SHIFT-F1 etc. work on Sun sym = XKeycodeToKeysym(disp, keycode, 0); } short code; if (sym == NoSymbol) code = 0; else code = Map_Sym_To_Code(sym); if (code == -1) {// try again with unshifted symbol // This makes SHIFT-R7 etc. work on Sun KeySym second_sym = XKeycodeToKeysym(disp, keycode, 0); code = Map_Sym_To_Code(second_sym); } if (code> 0) { // only support keyboard keys going down return create_input_char_from_code (code, state, Am_NEITHER, Am_NOT_MOUSE); } else { // in case is an illegal character or modifier if (code < 0) cout << "** Unknown keyboard symbol " << sym << " ignored\n" << flush; return Am_Input_Char(); // null means illegal } } Am_Input_Char create_input_char_from_mouse (unsigned int button, unsigned int state, Am_Button_Down down, Time time, Screen_Desc* screen) { int code = 0; if (button == Button1) code = Am_LEFT_MOUSE; else if (button == Button2) code = Am_MIDDLE_MOUSE; else if (button == Button3) code = Am_RIGHT_MOUSE; else { cerr << "** Unknown mouse button " << button << "." << endl; Am_Error (); } Am_Click_Count cnt = Check_Multi_Click(code, state, down, time, screen); return create_input_char_from_code (code, state, down, cnt); } // // // // // // // // // // // // // // // // // // // // // Main Input Event Handler // // // // // // // // // // // // // // // // // // // // void set_input_event (Am_Input_Event *ev, Am_Input_Char ic, int x, int y, unsigned long time, Am_Drawonable *draw) { ev->x = x; ev->y = y; ev->input_char = ic; ev->time_stamp = time; ev->draw = draw; if (Am_Debug_Print_Input_Events) cout << "\n " << ic << " x=" << x << " y=" << y << " time=" << time << " drawonable=" << draw << endl; } bool exit_if_stop_char(Am_Input_Char ic) { if (ic == Am_Stop_Character) { // cerr << "Got stop event: exiting Amulet main loop." << endl; Am_Main_Loop_Go = false; return true; } else return false; } void handle_selection_request (XEvent& ev, Am_Drawonable_Impl* draw) { // someone wants our selection. XEvent notify; Atom p; if (draw->screen->cut_data == NULL) p = None; else { p = ev.xselectionrequest.property; XChangeProperty(ev.xselectionrequest.display, //draw->screen->display, ev.xselectionrequest.requestor, ev.xselectionrequest.property, XA_STRING, 8, PropModeReplace, (unsigned char*)draw->screen->cut_data, strlen(draw->screen->cut_data)); } notify.xany.type = SelectionNotify; notify.xselection.display = ev.xselectionrequest.display; notify.xselection.send_event = True; notify.xselection.requestor = ev.xselectionrequest.requestor; notify.xselection.selection = ev.xselectionrequest.selection; notify.xselection.target = XA_STRING; notify.xselection.property = p; notify.xselection.time = ev.xselectionrequest.time; int error = XSendEvent(draw->screen->display, ev.xselectionrequest.requestor, True, 0, ¬ify); cerr << "send event result = " << error << endl; XSync(draw->screen->display, False); // I think we're possibly leaving the property we changed hanging. } Bool selection_event(Display* /* d */, XEvent *ev, char*) { if ((ev->xany.type == SelectionNotify) || ev->xany.type == SelectionRequest) return True; else return False; } char* Am_Drawonable_Impl::Get_Cut_Buffer() { if (offscreen) return NULL; // not meaningful for offscreen bitmaps // To get a selection properly, we need to send off a SelectionRequest event // (this is done with the call to XConvertSelection), and then wait for a // SelectionNotify event. Since we need to finish this routine synchronously // instead of waiting for a SelectionNotify event, we'll just wait until // any one event comes in, and if it's a SelectionNotify we'll take it // and get the selection, otherwise we'll put it back and pretend the request // never happened. This is a gross hack, but it seems to work fairly well // when running on my machine, everything locally. Probably it will fail more // if the selection is on a different machine than the Amulet program. if (this == screen->root) { // then we're in a root window cerr << "** Gem warning: Get_Cut_Buffer() won't work in a root window.\n"; return NULL; } // Make an atom to get the selection in. // Request the selection from the X server XConvertSelection(screen->display, XA_PRIMARY, XA_STRING, screen->cut_buffer, xlib_drawable, CurrentTime); XSync(screen->display, False); // Wait for an event, handling selection requests. // if it's a selection notify, get the selection XEvent ev; while (true) { while (!XPending (screen->display)); // XNextEvent(screen->display, &ev); if (XCheckIfEvent(screen->display, &ev, selection_event, NULL) == False // && XCheckIfEvent(screen->display, &ev, selection_event, NULL) == False) ) { // if we get here, we didn't get any selection notify event back. // should we use cut buffer0 here instead? cerr << "** missing selection notify event." << endl; // XPutBackEvent(screen->display, &ev); // checkifevent doesn't dequeue nonmatching events return NULL; } if (ev.xany.type == SelectionNotify) break; // if (ev.xany.type == SelectionRequest) { Am_Drawonable_Impl *draw = Get_Drawable_Backpointer(ev.xany.display, ev.xany.window); if (draw) handle_selection_request(ev, draw); // else handle_selection_request(ev, screen->root); // } } // if we get here, then we have a selection if (ev.xselection.property == None) { // then there is no selection value, use cut buffer 0 instead. cerr << "** No primary selection, using cut buffer." << endl; int n; char* tempstr = XFetchBytes(screen->display, &n); char* str = new char[n+1]; strcpy (str, tempstr); XFree(tempstr); return str; } else { // we have a selection value // Get the property's value. I got this from an email from // "S.Ramakrishnan" to bam@cs.cmu.edu char str[200]; char *buff; int actual_format; unsigned long num_items_ret, rest; Atom actual_type; long begin = 0; for (*str = 0, rest = 1; rest; strcat(str, buff), begin++, XFree(buff)) { XGetWindowProperty(screen->display, xlib_drawable, ev.xselection.property, begin, 1, True, AnyPropertyType, &actual_type, &actual_format, &num_items_ret, &rest, (unsigned char**)&buff); if (!buff) break; } buff = new char[strlen(str) + 1]; strcpy (buff, str); return buff; } } void Am_Handle_Event_Received (XEvent& event_return) { Am_Drawonable_Impl *draw = Get_Drawable_Backpointer(event_return.xany.display, event_return.xany.window); if (!draw) { if (Am_Debug_Print_Input_Events) cout << " Input ignored because no drawonable\n" << flush; return; } if (draw->ext_handler) { //call the external handler IN ADDITION to local handlers draw->ext_handler(&event_return); } Am_Input_Event_Handlers *evh = draw->event_handlers; Am_Input_Char ic; if (!evh) { if (Am_Debug_Print_Input_Events) cout << " Input ignored for " << draw << " because no Event_Handler\n" << flush; return; } switch (event_return.xany.type) { case KeyPress: ic = create_input_char_from_key (&event_return.xkey); if (ic.code != 0) { // then is a legal code if (exit_if_stop_char(ic)) return; set_input_event (Am_Current_Input_Event, ic, event_return.xkey.x, event_return.xkey.y, event_return.xkey.time, draw); draw->event_handlers->Input_Event_Notify(draw, Am_Current_Input_Event); } break; case ButtonPress: ic = create_input_char_from_mouse (event_return.xbutton.button, event_return.xbutton.state, Am_BUTTON_DOWN, event_return.xbutton.time, draw->screen); if (exit_if_stop_char(ic)) return; set_input_event (Am_Current_Input_Event, ic, event_return.xbutton.x, event_return.xbutton.y, event_return.xbutton.time, draw); draw->event_handlers->Input_Event_Notify(draw, Am_Current_Input_Event); break; case ButtonRelease: ic = create_input_char_from_mouse (event_return.xbutton.button, event_return.xbutton.state, Am_BUTTON_UP, event_return.xbutton.time, draw->screen); if (exit_if_stop_char(ic)) return; set_input_event (Am_Current_Input_Event, ic, event_return.xbutton.x, event_return.xbutton.y, event_return.xbutton.time, draw); draw->event_handlers->Input_Event_Notify(draw, Am_Current_Input_Event); break; case MotionNotify: ic = create_input_char_from_code (Am_MOUSE_MOVED, event_return.xmotion.state, Am_NEITHER, Am_SINGLE_CLICK); set_input_event (Am_Current_Input_Event, ic, event_return.xmotion.x, event_return.xmotion.y, event_return.xmotion.time, draw); draw->event_handlers->Input_Event_Notify(draw, Am_Current_Input_Event); break; case EnterNotify: ic = create_input_char_from_code (Am_MOUSE_ENTER_WINDOW, event_return.xcrossing.state, Am_NEITHER, Am_SINGLE_CLICK); set_input_event (Am_Current_Input_Event, ic, event_return.xcrossing.x, event_return.xcrossing.y, event_return.xcrossing.time, draw); draw->event_handlers->Input_Event_Notify(draw, Am_Current_Input_Event); break; case LeaveNotify: ic = create_input_char_from_code (Am_MOUSE_LEAVE_WINDOW, event_return.xcrossing.state, Am_NEITHER, Am_SINGLE_CLICK); set_input_event (Am_Current_Input_Event, ic, event_return.xcrossing.x, event_return.xcrossing.y, event_return.xcrossing.time, draw); draw->event_handlers->Input_Event_Notify(draw, Am_Current_Input_Event); break; case Expose: if (Am_Debug_Print_Input_Events) cout << " Exposure Event, x=" << event_return.xexpose.x << " y=" << event_return.xexpose.y << " height=" << event_return.xexpose.height << " drawonable=" << draw << endl; draw->event_handlers->Exposure_Notify(draw, // event_return.xexpose.count, event_return.xexpose.x, event_return.xexpose.y, event_return.xexpose.width, event_return.xexpose.height); break; case DestroyNotify: if (Am_Debug_Print_Input_Events) cout << " DestroyNotify, drawonable=" << draw << endl; draw->event_handlers->Destroy_Notify(draw); break; //// BUG: Not used // cases UnmapNotify and MapNotify were commented out // EAB: there does not appear to be a way to distinguish internally-generated // map/unmap requests from user-generated iconify/deiconify requests case UnmapNotify: if (Am_Debug_Print_Input_Events) cout << " UnmapNotify, drawonable=" << draw << endl; // draw->event_handlers->Unmap_Notify(draw); if (draw->expect_map_change) { draw->expect_map_change = false; } else { draw->iconify_notify(true); draw->event_handlers->Iconify_Notify(draw, true); } break; case MapNotify: if (Am_Debug_Print_Input_Events) cout << " MapNotify, drawonable=" << draw << endl; // draw->event_handlers->Map_Notify(draw, false); if (draw->expect_map_change) { draw->expect_map_change = false; } else { draw->iconify_notify(false); draw->event_handlers->Iconify_Notify(draw, false); } break; case ReparentNotify: { if (Am_Debug_Print_Input_Events) cout << " ReparentNotify, drawonable=" << draw << endl; int left, top, right, bottom, outer_left, outer_top; draw->Inquire_Window_Borders (left, top, right, bottom, outer_left, outer_top); draw->event_handlers->Frame_Resize_Notify(draw, left, top, right, bottom); break; } case ConfigureNotify: { // Configure events may come in for windows that haven't really // changed size or position, and in this case the events are ignored // (i.e., Gem does not pass them on to the user's event-handler). // For example, a 'valid' configure event may be percieved and // dispatched that causes the user's ConfigureNotify Event Handler // to set the size of the window, which in turn generates a 'bogus' // configure event. When the bogus event is percieved here, we // detect it by noticing that its values are equal to those already // installed in the drawonable, and throw it away. // // A Good ConfigureNotify handler should respond to the event // not by moving the window again, but by just setting its internal // state to correspond to where the window was moved to. The window // manager doesn't have to put the window where you request, or make // it the correct size. It tells you where it put it through a // ConfigureNotify event. If you try to force the WM to put it // where you want by looking at the configureNotify event to find // out where it actually put it, you might get walking windows // no matter what you do. // // Note: Gem specifications dictate that the "left" and "top" of a // window is the left and top of its frame (not necessarily its // drawable area). The "width" and "height" of a window is the // width and height of its drawable area. // Also, things drawn inside the window have their coordinates // relative to the drawable area's left and top. // int x, y, w, h, left, top, width, height, outer_left, outer_top, lb, tb, rb, bb; // We need to use inquire_window_borders to calculate the correct // x and y values, because X windows returns local coordinates // in the event, not global coordinates, so they're always (0,0). if (draw->Inquire_Window_Borders (lb, tb, rb, bb, outer_left, outer_top)) { x = outer_left; y = outer_top; w = event_return.xconfigure.width; h = event_return.xconfigure.height; draw->Get_Position (left, top); draw->Get_Size (width, height); // cout << "Configure notify: " << (void*)draw << " " << w << " " << h << " " << event_return.xany.window << endl; // Only generate a Gem configure-notify event if something changed if ((x != left) || (y != top) || (w != width) || (h != height)) { if (Am_Debug_Print_Input_Events) cout << " Configure Notify, x=" << x << " y=" << y << " height=" << height << " drawonable=" << draw << endl; draw->reconfigure (x, y, w, h); draw->event_handlers->Configure_Notify(draw, x, y, w, h); } } break; } case ClientMessage: // this is where we get window destroy messages // from the window manager. { // this is speed-inefficient, but it never happens, so who cares. Atom wm_delete_window = XInternAtom(event_return.xclient.display, "WM_DELETE_WINDOW", False); if ((unsigned long) event_return.xclient.data.l[0] != wm_delete_window) break; // not a delete window client message. draw->event_handlers->Destroy_Notify(draw); } break; case SelectionClear: // We get this event when we're forced to release our selection. // We'll ignore it for now. break; case SelectionRequest: handle_selection_request(event_return, draw); break; // next all the events we don't expect to occur case KeyRelease: case FocusIn: case FocusOut: case KeymapNotify: case VisibilityNotify: case MapRequest: case ConfigureRequest: case GravityNotify: case ResizeRequest: case CirculateNotify: case CirculateRequest: case PropertyNotify: case ColormapNotify: case MappingNotify: cout << "** Received event of unexpected type: " << event_names[event_return.type] << endl; case CreateNotify: case GraphicsExpose: case NoExpose: case SelectionNotify: // we get this here if get_cut_buffer misses it. // we do get these events unfortunately, so silently ignore them. break; } // end switch } Bool is_input_event (XEvent& event) { switch (event.xany.type) { case ButtonPress: case ButtonRelease: case KeyPress: case MotionNotify: case EnterNotify: case LeaveNotify: return true; default: return false; } } /* * Flush_Extra_Move_Events: If parameter is a mouse move event, then throw * away all other contiguous move events until * the last one, and leave event_return filled * with the last one. Disp_ version for multi- * screen, and UseX_ for not */ void Disp_Flush_Extra_Move_Events(XEvent& event_return) { int cnt = 0; if (event_return.xany.type == MotionNotify) { XEvent next_event_return; while (Scrn_Mgr.Pending (&next_event_return)) { if (next_event_return.xany.type == MotionNotify) { event_return = next_event_return; cnt ++; } else { Scrn_Mgr.Put_Event_Back (next_event_return); break; } } } if (Am_Debug_Print_Input_Events && cnt> 0) cout << " Multi Ignoring " << cnt << " move events\n" << flush; } void UseX_Flush_Extra_Move_Events(XEvent& event_return) { int cnt = 0; if (event_return.xany.type == MotionNotify) { XEvent next_event_return; while (XPending (Main_Display)) { XNextEvent (Main_Display, &next_event_return); if (next_event_return.xany.type == MotionNotify) { event_return = next_event_return; cnt ++; } else { XPutBackEvent(Main_Display, &next_event_return); break; } } } if (Am_Debug_Print_Input_Events && cnt> 0) cout << " Ignoring " << cnt << " move events\n" << flush; } /* * Process_Immediate_Event: Does not wait for an event, but processes * the first event in the queue and all non-input * events after it until an input event is seen. * The function returns when it encounters an input * event (excluding the case where the first event * is an input event) or when the queue is empty. */ void Am_Drawonable::Process_Immediate_Event () { XEvent event_return; if (More_Than_One_Display) { while (Scrn_Mgr.Pending (&event_return)) { Disp_Flush_Extra_Move_Events(event_return); Am_Handle_Event_Received (event_return); // If that was an input event, then process all the remaining // non-input events (and don't process another input event). if (is_input_event (event_return)) { while (Scrn_Mgr.Pending (&event_return)) { if (is_input_event (event_return)) { Scrn_Mgr.Put_Event_Back (event_return); return; } else Am_Handle_Event_Received (event_return); } return; } } } else { while (XPending (Main_Display)) { XNextEvent (Main_Display, &event_return); UseX_Flush_Extra_Move_Events(event_return); Am_Handle_Event_Received (event_return); // If that was an input event, then process all the remaining // non-input events (and don't process another input event). if (is_input_event (event_return)) { while (XPending (Main_Display)) { XNextEvent (Main_Display, &event_return); if (is_input_event (event_return)) { XPutBackEvent(Main_Display, &event_return); return; } else Am_Handle_Event_Received (event_return); } return; } } } } /* * Process_Event: waits for the next event, and processes exactly one * input event and all non-input events before and after * that input event before returning. For example * before after * xxxIyyyIzzz ---> Izzz * The function returns when it encounters a second input * event or when the queue is empty. */ // Changes for animation interactor, 3-20-96 af1x // Now Process_Event will time out as soon as deadline passes. // It will process as few as 0 events, and at maximum, all of the events // described above. It tries hard not to drop events on the floor, by only // exiting when it's done with event processing. void Am_Drawonable::Process_Event (const Am_Time& deadline) { XEvent event_return; if (!(deadline.Zero()) || More_Than_One_Display) { // Figure out when we stop processing Am_Time now = Am_Time::Now(); Am_Time timeout; if (deadline> now) timeout = deadline - now; Am_Time_Data* time_data = Am_Time_Data::Narrow(timeout); // Only wait as long as the timeout event_return.type = 0; Scrn_Mgr.Next_Event (&event_return, time_data->time); time_data->Release(); // doesn't always return appn event: could time out. if (!event_return.type) return; // we timed out. Disp_Flush_Extra_Move_Events(event_return); Am_Handle_Event_Received (event_return); if (deadline.Is_Past()) return; // If that was not an input event, then process all the remaining // non-input events until we have processed an input event. if (!is_input_event (event_return)) { while (Scrn_Mgr.Pending (&event_return)) { Am_Handle_Event_Received (event_return); if (is_input_event (event_return)) break; if (deadline.Is_Past()) return; } } // Process all remaining non-input events while (Scrn_Mgr.Pending (&event_return)) { if (is_input_event (event_return)) { Scrn_Mgr.Put_Event_Back (event_return); return; } else Am_Handle_Event_Received (event_return); if (deadline.Is_Past()) return; } } else { // single screen, no timeout value. XNextEvent (Main_Display, &event_return); UseX_Flush_Extra_Move_Events(event_return); Am_Handle_Event_Received (event_return); // If that was not an input event, then process all the remaining // non-input events until we have processed an input event. if (!is_input_event (event_return)) { while (XPending (Main_Display)) { XNextEvent (Main_Display, &event_return); Am_Handle_Event_Received (event_return); if (is_input_event (event_return)) break; } } // Process all remaining non-input events while (XPending (Main_Display)) { XNextEvent (Main_Display, &event_return); if (is_input_event (event_return)) { XPutBackEvent(Main_Display, &event_return); return; } else Am_Handle_Event_Received (event_return); } } } /* * Wait_For_Event: waits until the event queue is not empty. Will return * immediately if queue is already not empty. Does not * process anything */ // Note: this is never used anywhere. 3-26-96 af1x void Am_Drawonable::Wait_For_Event () { // FIX THIS TO USE AM_TIME long timeout_msec = 100; timeval timeout; timeout.tv_sec = timeout_msec / 1000; timeout.tv_usec = (timeout_msec % 1000) * 1000; if (More_Than_One_Display) Scrn_Mgr.Wait_For_Event (timeout); else { XEvent event_return; XPeekEvent (Main_Display, &event_return); } } void Am_Drawonable::Main_Loop () { Am_Time no_timeout; while (Am_Main_Loop_Go) Process_Event (no_timeout); } // // // // // // // // // // // // // // // // // // // // // Am_Drawonable member functions // // // // // // // // // // // // // // // // // // // // void Am_Drawonable_Impl::Initialize_Event_Mask () { want_enter_leave = false; want_multi_window = false; want_move = false; current_event_mask = ( ButtonPressMask | ButtonReleaseMask | KeyPressMask | ExposureMask | StructureNotifyMask ); // current_event_mask can be now passed to XCreateWindow as part // of the XSetWindowAttributes } void Am_Drawonable_Impl::set_drawable_event_mask () { if (want_move) { unsigned int pointer_active_mask; current_event_mask = // *report-motion-em* ExposureMask | PointerMotionMask | ButtonPressMask | ButtonReleaseMask | KeyPressMask | StructureNotifyMask; pointer_active_mask = // *report-motion-pem* (unsigned int) (ButtonPressMask | ButtonReleaseMask | PointerMotionMask); if (want_enter_leave) { // add enter leave masks current_event_mask |= EnterWindowMask | LeaveWindowMask; pointer_active_mask |= (unsigned int)(EnterWindowMask | LeaveWindowMask); } if (want_multi_window) { // add owner-grab-button current_event_mask |= OwnerGrabButtonMask; // don't use OwnerGrabButtonMask in the p_a_m mask for grab } // this will change an active grab if one is in process because // changing the window's event mask will have no effect if there is // an active grab already. Active grabs happen whenever // the mouse button is pressed down. XChangeActivePointerGrab (screen->display, pointer_active_mask, None, CurrentTime); } else { // don't want motion events, don't need a pointer mask current_event_mask = // *ignore-motion-em* ExposureMask | ButtonPressMask | ButtonReleaseMask | KeyPressMask | StructureNotifyMask; if (want_enter_leave) current_event_mask |= EnterWindowMask | LeaveWindowMask; if (want_multi_window) current_event_mask |= OwnerGrabButtonMask; } if (Am_Debug_Print_Input_Events) cout << "Changing Event Mask to " << current_event_mask << " for " << this << endl; // now call X to install event mask XSelectInput(screen->display, xlib_drawable, current_event_mask); // After the XSelectInput, you need to force-output because when // using the backgroup m-e-l process, otherwise this doesn't get noticed. // *no m-e-l yet* this->Flush_Output(); // Ignore NoExpose and GraphicsExpose events (double buffering) XSetGraphicsExposures(screen->display, screen->gc, false); } void Am_Drawonable_Impl::Set_Enter_Leave ( bool want_enter_leave_events ) { if (want_enter_leave_events != want_enter_leave) { want_enter_leave = want_enter_leave_events; this->set_drawable_event_mask (); } } void Am_Drawonable_Impl::Set_Want_Move ( bool want_move_events ) { if (want_move != want_move_events) { // then changing want_move = want_move_events; this->set_drawable_event_mask (); } } void Am_Drawonable_Impl::Set_Multi_Window ( bool want_multi ) { if (want_multi != want_multi_window) { // then changing want_multi_window = want_multi; this->set_drawable_event_mask (); } } void Am_Drawonable_Impl::Discard_Pending_Events() { // * NIY * cout << "** Discarding pending input events NIY \n"; } void Am_Drawonable_Impl::Set_Input_Dispatch_Functions (Am_Input_Event_Handlers* evh) { event_handlers = evh; } void Am_Drawonable_Impl::Get_Input_Dispatch_Functions (Am_Input_Event_Handlers*& evh) { evh = event_handlers; } //Find the child-most drawonable at the current cursor position Am_Drawonable* Am_Drawonable_Impl::Get_Drawonable_At_Cursor() { Display* display = screen->display; Window parent, root_return, child_return; int root_x_return, root_y_return, win_x_return, win_y_return; unsigned int mask_return; parent = screen->root->xlib_drawable; Am_Drawonable_Impl *return_draw = NULL; Am_Drawonable_Impl *draw = NULL; // need to find leaf-most window that contains the cursor, keep // going down until fail while(true) { if(!XQueryPointer (display, parent, &root_return, &child_return, &root_x_return, &root_y_return, &win_x_return, &win_y_return, &mask_return)) return NULL; //if XQueryPointer returns false, then not on right screen if (child_return) { if (child_return == parent) { // looping, return last good drawonable return return_draw; } else draw = Get_Drawable_Backpointer(display, child_return); } else return return_draw; //no child found, return last good drawonable if (draw) { //save in case this is the last good one return_draw = draw; } //have a child, loop to see if can find a child of that child parent = child_return; } } // This function is called during window creation, and when a window changes // from invisible to visible. If you do not wait for the MapNotify event // before drawing, then your window may come up blank. // void wait_for_mapnotify (Display *dpy, Window *xlib_window) { XEvent event_return; while(1) { XPeekIfEvent(dpy, &event_return, is_mapnotify, (XPointer)xlib_window); // XPeekIfEvent may have returned without setting event_return (in other // words, is_mapnotify returned False). if (event_return.type == MapNotify) return; } }

AltStyle によって変換されたページ (->オリジナル) /