1616
1717
1818class Actions :
19- COPY = frozenset ({"copy" , "clipboard_copy" , "gdocs_copy" , "menu_copy" , "edit_copy" })
20- CUT = frozenset ({"cut" , "clipboard_cut" , "gdocs_cut" , "menu_cut" , "edit_cut" })
19+ COPY = frozenset ({"copy" , "clipboard_copy" , "gdocs_copy" , "menu_copy" , "edit_copy" , "contextmenu_copy" })
20+ CUT = frozenset ({"cut" , "clipboard_cut" , "gdocs_cut" , "menu_cut" , "edit_cut" , "contextmenu_cut" })
2121 PASTE = frozenset ({"paste" , "clipboard_paste" , "gdocs_paste" , "insert_from_clipboard" })
2222 MENU_PASTE = frozenset ({"menu_paste" , "edit_paste" , "contextmenu_paste" })
2323 GDOCS_SAVE = "google_docs_save"
@@ -72,14 +72,28 @@ def is_copy(client):
7272 action = event_action (client )
7373 if action in Actions .COPY :
7474 return True
75- return _is_key_combo (keys_info (client ), "c" , "KeyC" , 67 )
75+
76+ if _menu_item_startswith (client , "copy" ):
77+ return True
78+
79+ info = keys_info (client )
80+ return info ["event_type" ] == "keydown" and info ["ctrl_or_meta" ] and (
81+ info ["key" ] == "c" or info ["code" ] == "KeyC" or info ["key_code" ] == 67
82+ )
7683
7784
7885def is_cut (client ):
7986 action = event_action (client )
8087 if action in Actions .CUT :
8188 return True
82- return _is_key_combo (keys_info (client ), "x" , "KeyX" , 88 )
89+
90+ if _menu_item_startswith (client , "cut" ):
91+ return True
92+
93+ info = keys_info (client )
94+ return info ["event_type" ] == "keydown" and info ["ctrl_or_meta" ] and (
95+ info ["key" ] == "x" or info ["code" ] == "KeyX" or info ["key_code" ] == 88
96+ )
8397
8498
8599def is_paste_keyboard (client ):
@@ -89,6 +103,23 @@ def is_paste_keyboard(client):
89103 return _is_key_combo (keys_info (client ), "v" , "KeyV" , 86 )
90104
91105
106+ def _is_google_menu_item (client ):
107+ mc = client .get ("mouseclick" ) or {}
108+ class_name = str (mc .get ("target.className" ) or "" )
109+ return (
110+ "goog-menuitem-label" in class_name
111+ or "goog-menuitem-content" in class_name
112+ or "goog-menuitem" in class_name
113+ )
114+
115+
116+ def _menu_item_startswith (client , action_name ):
117+ if event_action (client ) != "mouseclick" :
118+ return False
119+ if not _is_google_menu_item (client ):
120+ return False
121+ return _menu_item_text (client ).startswith (action_name )
122+
92123def _menu_item_text (client ):
93124 """Normalize a Google menu item's innerText — strips extra whitespace so
94125 labels like 'Paste without formatting Ctrl+Shift+V' collapse cleanly."""
@@ -106,23 +137,14 @@ def looks_like_menu_paste(client):
106137 return True
107138
108139 if action == "mouseclick" :
109- mc = client .get ("mouseclick" ) or {}
110- inner_text = _menu_item_text (client )
111- class_name = str (mc .get ("target.className" ) or "" )
112-
113- # Match any Google Docs menu item element
114- is_google_menu_item = (
115- "goog-menuitem-label" in class_name
116- or "goog-menuitem-content" in class_name
117- or "goog-menuitem" in class_name
118- )
119140
141+ inner_text = _menu_item_text (client )
120142 # startswith("paste") catches all variants:
121143 # "paste"
122144 # "paste ctrl+v"
123145 # "paste without formatting"
124146 # "paste without formatting ctrl+shift+v"
125- if is_google_menu_item and inner_text .startswith ("paste" ):
147+ if _is_google_menu_item ( client ) and inner_text .startswith ("paste" ):
126148 return True
127149
128150 return False
0 commit comments