169169 align-items : flex-start;
170170 }
171171 }
172+
173+ /* Feature Panel */
174+ .feature-panel {
175+ background : rgba (255 , 255 , 255 , 0.1 );
176+ backdrop-filter : blur (10px );
177+ padding : 15px ;
178+ border-radius : 10px ;
179+ color : white;
180+ margin-top : 20px ;
181+ }
182+
183+ .feature-panel h3 {
184+ margin-bottom : 15px ;
185+ font-size : 1.2rem ;
186+ }
187+
188+ .button-grid {
189+ display : grid;
190+ grid-template-columns : repeat (auto-fit, minmax (200px , 1fr ));
191+ gap : 10px ;
192+ margin-bottom : 15px ;
193+ }
194+
195+ .test-button {
196+ background : rgba (76 , 175 , 80 , 0.8 );
197+ border : none;
198+ padding : 12px 20px ;
199+ border-radius : 5px ;
200+ color : white;
201+ cursor : pointer;
202+ font-size : 14px ;
203+ font-weight : 500 ;
204+ transition : all 0.2s ;
205+ }
206+
207+ .test-button : hover {
208+ background : rgba (76 , 175 , 80 , 1 );
209+ transform : translateY (-2px );
210+ box-shadow : 0 4px 12px rgba (0 , 0 , 0 , 0.3 );
211+ }
212+
213+ .test-button : active {
214+ transform : translateY (0 );
215+ }
216+
217+ .event-log {
218+ background : rgba (0 , 0 , 0 , 0.3 );
219+ border-radius : 5px ;
220+ padding : 10px ;
221+ max-height : 150px ;
222+ overflow-y : auto;
223+ font-family : 'Monaco' , 'Menlo' , monospace;
224+ font-size : 12px ;
225+ }
226+
227+ .event-log .event {
228+ padding : 4px 0 ;
229+ border-bottom : 1px solid rgba (255 , 255 , 255 , 0.1 );
230+ }
231+
232+ .event-log .event : last-child {
233+ border-bottom : none;
234+ }
235+
236+ .event-log .event-type {
237+ color : # 4caf50 ;
238+ font-weight : bold;
239+ }
240+
241+ .event-log .event-data {
242+ color : # e0e0e0 ;
243+ }
244+
245+ .clear-log {
246+ background : rgba (244 , 67 , 54 , 0.8 );
247+ border : none;
248+ padding : 8px 16px ;
249+ border-radius : 5px ;
250+ color : white;
251+ cursor : pointer;
252+ font-size : 12px ;
253+ margin-top : 10px ;
254+ }
255+
256+ .clear-log : hover {
257+ background : rgba (244 , 67 , 54 , 1 );
258+ }
172259 </ style >
173260 </ head >
174261 < body >
@@ -193,21 +280,27 @@ <h1>🖥️ Ghostty Terminal</h1>
193280 < div id ="terminal-container "> </ div >
194281 </ div >
195282
196- < div class ="info-box ">
197- < h3 > 📚 Available Commands</ h3 >
198- < ul >
199- < li > < code > ls</ code > , < code > ls -la</ code > - List directory contents</ li >
200- < li >
201- < code > cd <path></ code > - Change directory (supports < code > ~</ code > ,
202- < code > /</ code > , < code > ..</ code > )
203- </ li >
204- < li > < code > pwd</ code > - Print working directory</ li >
205- < li > < code > cat <file></ code > - Display file contents</ li >
206- < li > < code > grep</ code > , < code > find</ code > , < code > tree</ code > - Search and explore</ li >
207- < li > < code > whoami</ code > , < code > hostname</ code > , < code > date</ code > - System info</ li >
208- < li > < code > clear</ code > - Clear screen</ li >
209- < li > < code > exit</ code > - Close connection</ li >
210- </ ul >
283+ < div class ="feature-panel ">
284+ < h3 > 🎯 xterm.js API Features</ h3 >
285+ < div class ="button-grid ">
286+ < button class ="test-button " id ="btn-paste "> 📋 Test paste()</ button >
287+ < button class ="test-button " id ="btn-blur "> 👁️ Test blur()</ button >
288+ < button class ="test-button " id ="btn-input "> ⌨️ Test input()</ button >
289+ < button class ="test-button " id ="btn-select "> 🔤 Test select()</ button >
290+ < button class ="test-button " id ="btn-selectLines "> 📄 Test selectLines()</ button >
291+ < button class ="test-button " id ="btn-getSelectionPos "> 📍 Get Selection Position</ button >
292+ < button class ="test-button " id ="btn-setTitle "> 🏷️ Set Title (OSC)</ button >
293+ < button class ="test-button " id ="btn-customHandler "> 🔧 Toggle Custom Handler</ button >
294+ </ div >
295+ < div >
296+ < strong style ="display: block; margin-bottom: 8px "
297+ > Event Log (onKey, onTitleChange):</ strong
298+ >
299+ < div class ="event-log " id ="event-log ">
300+ < div style ="opacity: 0.5; text-align: center "> Events will appear here...</ div >
301+ </ div >
302+ < button class ="clear-log " id ="btn-clear-log "> Clear Log</ button >
303+ </ div >
211304 </ div >
212305
213306 < div class ="warning ">
@@ -532,6 +625,136 @@ <h3>📚 Available Commands</h3>
532625 // - Auto-copy to clipboard on selection
533626 // - Ctrl+C / Cmd+C to copy (via browser context menu)
534627
628+ // =========================================================================
629+ // Event Logging & Handlers
630+ // =========================================================================
631+
632+ let customHandlerEnabled = false ;
633+
634+ function logEvent ( type , data ) {
635+ const eventLog = document . getElementById ( 'event-log' ) ;
636+ const eventDiv = document . createElement ( 'div' ) ;
637+ eventDiv . className = 'event' ;
638+
639+ const timestamp = new Date ( ) . toLocaleTimeString ( ) ;
640+ const dataStr = typeof data === 'object' ? JSON . stringify ( data ) : String ( data ) ;
641+
642+ eventDiv . innerHTML = `
643+ <span style="opacity: 0.6;">[${ timestamp } ]</span>
644+ <span class="event-type">${ type } :</span>
645+ <span class="event-data">${ dataStr } </span>
646+ ` ;
647+
648+ eventLog . appendChild ( eventDiv ) ;
649+ eventLog . scrollTop = eventLog . scrollHeight ;
650+
651+ // Keep only last 50 events
652+ while ( eventLog . children . length > 50 ) {
653+ eventLog . removeChild ( eventLog . firstChild ) ;
654+ }
655+ }
656+
657+ function setupEventHandlers ( ) {
658+ // Event: onKey - fires on every keypress
659+ term . onKey ( ( e ) => {
660+ logEvent ( 'onKey' , `key="${ e . key } " ctrl=${ e . domEvent . ctrlKey } alt=${ e . domEvent . altKey } ` ) ;
661+ } ) ;
662+
663+ // Event: onTitleChange - fires when terminal title changes via OSC sequences
664+ term . onTitleChange ( ( title ) => {
665+ logEvent ( 'onTitleChange' , title ) ;
666+ document . title = `${ title } - Ghostty Terminal` ;
667+ } ) ;
668+
669+ // Button handlers
670+ document . getElementById ( 'btn-paste' ) . addEventListener ( 'click' , ( ) => {
671+ term . paste ( 'Pasted text from paste() method!\n' ) ;
672+ logEvent ( 'Action' , 'Called paste()' ) ;
673+ } ) ;
674+
675+ document . getElementById ( 'btn-blur' ) . addEventListener ( 'click' , ( ) => {
676+ term . blur ( ) ;
677+ logEvent ( 'Action' , 'Called blur() - terminal lost focus' ) ;
678+ } ) ;
679+
680+ document . getElementById ( 'btn-input' ) . addEventListener ( 'click' , ( ) => {
681+ term . input ( 'echo "Input from input() method"\n' , true ) ;
682+ logEvent ( 'Action' , 'Called input() with wasUserInput=true' ) ;
683+ } ) ;
684+
685+ document . getElementById ( 'btn-select' ) . addEventListener ( 'click' , ( ) => {
686+ // Select 20 characters starting at column 0, row 0
687+ // This selects from the current cursor position backwards 20 chars
688+ const cursor = term . wasmTerm . getCursor ( ) ;
689+ const startRow = Math . max ( 0 , cursor . y - 1 ) ;
690+ term . select ( 0 , startRow , 30 ) ;
691+ logEvent (
692+ 'Action' ,
693+ `Called select(0, ${ startRow } , 30) - selects 30 chars from row ${ startRow } `
694+ ) ;
695+ } ) ;
696+
697+ document . getElementById ( 'btn-selectLines' ) . addEventListener ( 'click' , ( ) => {
698+ // Select last 3 visible lines
699+ const cursor = term . wasmTerm . getCursor ( ) ;
700+ const endRow = cursor . y ;
701+ const startRow = Math . max ( 0 , endRow - 2 ) ;
702+ term . selectLines ( startRow , endRow ) ;
703+ logEvent ( 'Action' , `Called selectLines(${ startRow } , ${ endRow } ) - selects 3 lines` ) ;
704+ } ) ;
705+
706+ document . getElementById ( 'btn-getSelectionPos' ) . addEventListener ( 'click' , ( ) => {
707+ const pos = term . getSelectionPosition ( ) ;
708+ if ( pos ) {
709+ logEvent (
710+ 'getSelectionPosition' ,
711+ `start=(${ pos . start . x } ,${ pos . start . y } ) end=(${ pos . end . x } ,${ pos . end . y } )`
712+ ) ;
713+ } else {
714+ logEvent ( 'getSelectionPosition' , 'No selection' ) ;
715+ }
716+ } ) ;
717+
718+ document . getElementById ( 'btn-setTitle' ) . addEventListener ( 'click' , ( ) => {
719+ // Send OSC 2 sequence to change title
720+ const newTitle = `Ghostty Terminal ${ new Date ( ) . toLocaleTimeString ( ) } ` ;
721+ term . write ( `\x1b]2;${ newTitle } \x07` ) ;
722+ logEvent ( 'Action' , `Sent OSC 2 sequence with title: ${ newTitle } ` ) ;
723+ } ) ;
724+
725+ document . getElementById ( 'btn-customHandler' ) . addEventListener ( 'click' , ( ) => {
726+ customHandlerEnabled = ! customHandlerEnabled ;
727+
728+ if ( customHandlerEnabled ) {
729+ // Custom handler that blocks Ctrl+K
730+ term . attachCustomKeyEventHandler ( ( event ) => {
731+ if ( event . ctrlKey && event . key === 'k' ) {
732+ logEvent ( 'CustomHandler' , 'Blocked Ctrl+K' ) ;
733+ return true ; // Block default handling
734+ }
735+ return false ; // Allow default handling
736+ } ) ;
737+ logEvent ( 'Action' , 'Custom handler enabled (blocks Ctrl+K)' ) ;
738+ document . getElementById ( 'btn-customHandler' ) . style . background =
739+ 'rgba(33, 150, 243, 0.8)' ;
740+ } else {
741+ term . attachCustomKeyEventHandler ( undefined ) ;
742+ logEvent ( 'Action' , 'Custom handler disabled' ) ;
743+ document . getElementById ( 'btn-customHandler' ) . style . background =
744+ 'rgba(76, 175, 80, 0.8)' ;
745+ }
746+ } ) ;
747+
748+ document . getElementById ( 'btn-clear-log' ) . addEventListener ( 'click' , ( ) => {
749+ document . getElementById ( 'event-log' ) . innerHTML =
750+ '<div style="opacity: 0.5; text-align: center;">Log cleared</div>' ;
751+ } ) ;
752+
753+ // Expose terminal to console for debugging
754+ window . term = term ;
755+ console . log ( 'Terminal exposed to window.term for debugging' ) ;
756+ }
757+
535758 // =========================================================================
536759 // Initialization
537760 // =========================================================================
@@ -603,6 +826,9 @@ <h3>📚 Available Commands</h3>
603826 // Text selection is now built-in - no need to enable it!
604827 // You can use term.getSelection(), term.selectAll(), etc.
605828
829+ // Hook up event listeners and test buttons
830+ setupEventHandlers ( ) ;
831+
606832 // Connect to WebSocket server
607833 connect ( ) ;
608834 } catch ( error ) {
0 commit comments