/* Extension LinuxMouse Author Carlo Hogeveen Website eCarlo.nl/tse Compatibility Linux TSE v4.50rc24 upwards for mouse in menus, "Advanced Linux distributions", like WSL2 and typically non-server distributions, and "advanced Terminals", like Putty. Version v1.1 27 Jun 2025 (Attempt to use SGR and Query(MouseX/Y)) If a Linux distribution and its terminal are "sufficiently advanced", then this extension makes TSE go to a clicked position in the text and it will scroll the text if the mouse wheel is recognized. This version attempts to use SGR mouse reporting to overcome the X10 column 93/94 limitation, relying on TSE's internal mouse coordinate query functions (Query(MouseX) and Query(MouseY)). DISCLAIMERS - There are two reasons the extension might not work at all: - It depends on Linux distribution capabilities that are typically not found in Linux server distributions. - For "Terminal" programs it depends on newer terminal features. - The extension has one major flaw (might be fixed by SGR): It makes the key and other function keys unusable (if using Query(Key) method). This might be resolved if Query(MouseX/Y) work. - It has minor flaws. INSTALLATION Copy this file to TSE's "mac" directory. Open the file in TSE and apply the Macro -> Compile menu. Either temporarily Macro -> Load -> "LinuxMouse", or add "LinuxMouse" to the Macro -> AutoLoad List menu and restart TSE. In your user interface file (Help -> About -> "ui:") - Optionally assign another key to the definition of the key. Compile the user interface file you modified. - For mouse scrolling to work you must define the and keys. In Windows TSE their default key definitions are: RollUp(Max(GetWheelScrollLines(), 1)) RollDown(Max(GetWheelScrollLines(), 1)) Compile the user interface file you modified. DETAILS The extension does not support columns and rows past 223. (This detail might become obsolete if SGR works correctly) For technical details about the LinuxMouse extension see the source of the LinuxMouseTest extension: https://ecarlo.nl/tse/DemosAndTests.html#LinuxMouseTest TODO MUST SHOULD - A solution for making F1 and other function keys usable. (Hopefully Query(MouseX/Y) solves this) - Mouse cannot select menu File -> Show File Info. - After mouse scrolling the next key does an extra scroll. - Clicking in a list is a line off. - The scroll wheel does nothing in a list. - Clicking a prompt's corner X does not close it. - Configuration options to assign functionality to mouse buttons. - Distinguish other mouse buttons than not-a-mouse-wheel and a-mouse-wheel. COULD - Try to make mouse marking work. WONT HISTORY v1.1 27 Jun 2025 - SIGNIFICANT CHANGE: Switched to SGR mouse reporting (ESC[?1006h). - ATTEMPT: Uses Query(MouseX) and Query(MouseY) to get coordinates, assuming TSE's internal parser can now handle SGR sequences correctly. This completely bypasses the problematic Query(Key) - 32 decoding. - DEBUGGING: Message calls added to confirm coordinate source. v1.0 27 Jun 2025 - Added debugging output (Message(mouse_x, " ", mouse_y)) to do_mouse_action and raw key value messages within after_getkey to help with coordinate verification. v0.9 27 Jun 2025 - Vertical scrollbar detection updated to use a fixed absolute column value of 134, based on user feedback. Assumes mouse_x is 1-indexed. v0.8 27 Jun 2025 - Refined vertical scrollbar detection: Changed 'mouse_x == (window_x2 - 1)' back to 'mouse_x == window_x2', assuming both mouse_x and Query(WindowCols) are 1-indexed. v0.7 27 Jun 2025 - Fixed vertical scrollbar detection: Changed 'mouse_x == window_x2' to 'mouse_x == (window_x2 - 1)' assuming 0-indexed mouse_x and 1-indexed WindowCols. v0.6 27 Jun 2025 - Adapted for vertical scrollbar clicking using GoToLine() to control the view, as ViewLine() is not a native SAL command. v0.5 18 Oct 2024 - Initial release. */ // Start of compatibility restrictions and mitigations ... #ifndef LINUX Error: This macro requires LINUX TSE. #endif #ifndef INTERNAL_VERSION #define INTERNAL_VERSION 0 #endif // End of compatibility restrictions and mitigations. // Constants and semi-constants // None. // Global variables integer num_ignorable_keys = 0 // Still needed for the F1 key workaround and initial mouse sequence detection integer mouse_event = 0 integer mouse_x = 0 integer mouse_y = 0 proc ignore_current_key() BreakHookChain() Set(Key, -1) end ignore_current_key // This procedure is still needed for decoding button states if we get them from Query(Key) // but ideally, TSE's internal mouse variables would provide button info more cleanly. // This might need adjustment if Query(Mouse...) provides button status differently. string proc decode_mouse_event(integer encoded_mouse_event) string decoded_mouse_event [29] = '' string modifier_keys [13] = '' case encoded_mouse_event & 11000011b when 00000000b decoded_mouse_event = 'Button1Pressed' when 00000001b decoded_mouse_event = 'Button2Pressed' when 00000010b decoded_mouse_event = 'Button3Pressed' when 00000011b decoded_mouse_event = 'ButtonReleased' when 01000000b decoded_mouse_event = 'WheelUp' when 01000001b decoded_mouse_event = 'WheelDown' when 01000010b decoded_mouse_event = 'Button6Pressed' when 01000011b decoded_mouse_event = 'Button7Pressed' when 10000000b decoded_mouse_event = 'Button8Pressed' when 10000001b decoded_mouse_event = 'Button9Pressed' when 10000010b decoded_mouse_event = 'Button10Pressed' when 10000011b decoded_mouse_event = 'Button11Pressed' otherwise decoded_mouse_event = 'SomeButtonEvent' endcase if encoded_mouse_event & 10000b modifier_keys = 'Ctrl' endif if encoded_mouse_event & 1000b modifier_keys = modifier_keys + 'Alt' endif if encoded_mouse_event & 100b modifier_keys = modifier_keys + 'Shift' endif if modifier_keys <> '' decoded_mouse_event = modifier_keys + ' ' + decoded_mouse_event endif return(decoded_mouse_event) end decode_mouse_event #if INTERNAL_VERSION < 12390 // TSE 4.50rc24 proc goto_mouse_cursor() integer first_editwindow_line = 0 integer first_editwindow_pos = 0 integer new_cursor_line = 0 integer new_cursor_pos = 0 first_editwindow_pos = 1 + CurrXoffset() first_editwindow_line = CurrLine() - CurrRow() + 1 new_cursor_pos = first_editwindow_pos + (mouse_x - Query(WindowX1)) new_cursor_line = first_editwindow_line + (mouse_y - Query(WindowY1)) if CurrPos() <> new_cursor_pos if CurrPos() < new_cursor_pos Right(new_cursor_pos - CurrPos()) else Left(CurrPos() - new_cursor_pos) endif endif if CurrLine() <> new_cursor_line if CurrLine() < new_cursor_line Down(new_cursor_line - CurrLine()) else Up(CurrLine() - new_cursor_line) endif endif end goto_mouse_cursor #endif proc do_mouse_action() string decoded_mouse_event [30] = '' integer window_y1 = Query(WindowY1) // Top row of the window integer window_y2 = Query(WindowRows) // Bottom row of the window (total height in rows) integer total_lines = NumLines() // Total lines in the buffer integer lines_in_window = window_y2 - window_y1 + 1 // Height of the window in rows integer target_line = 0 integer desired_top_line = 0 decoded_mouse_event = decode_mouse_event(mouse_event) // --- DEBUGGING OUTPUT --- // This will show coordinates as obtained by TSE's internal Query(MouseX/Y) Message("Mouse Action: Event=", decoded_mouse_event, " | X=", mouse_x, " Y=", mouse_y) // ------------------------ if not Pos('Released', decoded_mouse_event) if Pos('WheelUp' , decoded_mouse_event) if QueryEditState() == 0 PushKey() endif elseif Pos('WheelDown', decoded_mouse_event) if QueryEditState() == 0 PushKey() endif elseif Pos('Button1Pressed', decoded_mouse_event) // Left button click if QueryEditState() == 0 // Check if the click is on the vertical scrollbar area. // Using the absolute column value 134 provided by the user. // Assumes mouse_x is 1-indexed. if mouse_x == 134 // <--- Using absolute column value // Calculate the target line based on the click position in the scrollbar // mouse_y is relative to the screen, so we need to adjust it to be relative to the window. target_line = ((mouse_y - window_y1) * total_lines) / lines_in_window + 1 // Ensure target_line is within valid bounds if target_line < 1 target_line = 1 endif if target_line > total_lines target_line = total_lines endif // Calculate the line that should be at the top of the window // to make 'target_line' appear roughly in the center. if target_line <= (lines_in_window / 2) desired_top_line = 1 // If target_line is in the first half of the window, show from line 1 else // Otherwise, calculate the line that should be at the top to center target_line desired_top_line = target_line - (lines_in_window / 2) endif // Clamp desired_top_line to valid range if desired_top_line < 1 desired_top_line = 1 endif // Ensure we don't try to scroll past the very end of the buffer if desired_top_line + lines_in_window - 1 > total_lines desired_top_line = total_lines - lines_in_window + 1 if desired_top_line < 1 desired_top_line = 1 // For very small files (less than window height) endif endif // Move the cursor to the calculated 'desired_top_line'. // This will automatically scroll the editor's view // so that desired_top_line is at the very top of the window. GoToLine(desired_top_line) else // Original logic for clicking within the text area #if INTERNAL_VERSION >= 12390 // TSE 4.50rc24 // For TSE 4.50rc24 and above, TSE is expected to handle mouse coords internally. // We just let PushKey() do its magic with TSE's internal MouseX/Y. PushKey() #else // For older TSE versions, we still use the macro's goto_mouse_cursor goto_mouse_cursor() #endif endif endif endif endif end do_mouse_action proc after_getkey() integer current_key = Query(Key) case current_key when ignore_current_key() num_ignorable_keys = 3 when -1, 2047, 32767, 65535, 144967679 // Key -1 has "synonyms". NoOp() otherwise // We are trying to use Query(MouseX) and Query(MouseY) instead of decoding raw keys. // The num_ignorable_keys logic might primarily be useful for old X10 mouse, // or to detect that a mouse event *sequence* has started, so TSE has updated MouseX/Y. // If INTERNAL_VERSION >= 12390, we assume TSE updates Query(MouseX/Y) internally. #if INTERNAL_VERSION >= 12390 // Check if TSE has reported a mouse event key that implies internal mouse variables are updated. // A common pattern is that mouse events also come with a distinct key value (e.g., ) // or trigger the mouse hook. // We can check if mouse_event is populated by the raw Query(Key) processing (like leading to num_ignorable_keys=3) // OR if Query(Key) itself returns a mouse-related key (e.g. ). // Let's rely on num_ignorable_keys being set from an initial sequence character. if num_ignorable_keys > 0 // The initial sequence part has been captured by the F1/raw key check. // Now, try to get the actual mouse coordinates from TSE's internal state. case num_ignorable_keys when 3 // This is usually the button event byte mouse_event = Query(Key) // Still capture this raw button value for decode_mouse_event Message("DEBUG: Raw Mouse Event Byte: ", mouse_event) when 2 // This is usually the X coord byte // Try to get X directly from TSE's internal variable mouse_x = Query(MouseX) Message("DEBUG: Attempt Query(MouseX): ", mouse_x) // If Query(MouseX) is 0 or some default, you might fallback to Query(Key) - 32 // but that's what caused the 4072 problem. Best to rely on Query(MouseX). when 1 // This is usually the Y coord byte // Try to get Y directly from TSE's internal variable mouse_y = Query(MouseY) Message("DEBUG: Attempt Query(MouseY): ", mouse_y) do_mouse_action() endcase ignore_current_key() num_ignorable_keys = num_ignorable_keys - 1 endif #else // Older TSE versions, must rely on Query(Key) - 32 if num_ignorable_keys case num_ignorable_keys when 3 mouse_event = Query(Key) when 2 mouse_x = Query(Key) - 32 when 1 mouse_y = Query(Key) - 32 do_mouse_action() endcase ignore_current_key() num_ignorable_keys = num_ignorable_keys - 1 endif #endif endcase end after_getkey proc WhenPurged() // Disable SGR mouse tracking fWrite(_STDOUT_, Chr(27) + "[?1006l") // Disable X10 basic mouse tracking (good practice to turn off anything enabled) fWrite(_STDOUT_, Chr(27) + "[?1000l") // fflush(_STDOUT_) // Ensure output is sent end WhenPurged proc WhenLoaded() // Enable SGR mouse tracking (prefer SGR over X10 for higher coordinates) fWrite(_STDOUT_, Chr(27) + "[?1006h") // Also enable basic mouse tracking (X10), as some terminals might require it // or TSE might implicitly rely on it for some features. // This might also explain why the old macro worked up to 93 columns. fWrite(_STDOUT_, Chr(27) + "[?1000h") // fflush(_STDOUT_) // Ensure output is sent Hook(_AFTER_GETKEY_ , after_getkey) Hook(_ON_ABANDON_EDITOR_, WhenPurged ) // Stop mouse tracking after editor. end WhenLoaded proc Main() end Main