2 * Jexer - Java Text User Interface
4 * The MIT License (MIT)
6 * Copyright (C) 2019 Kevin Lamonte
8 * Permission is hereby granted, free of charge, to any person obtaining a
9 * copy of this software and associated documentation files (the "Software"),
10 * to deal in the Software without restriction, including without limitation
11 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
12 * and/or sell copies of the Software, and to permit persons to whom the
13 * Software is furnished to do so, subject to the following conditions:
15 * The above copyright notice and this permission notice shall be included in
16 * all copies or substantial portions of the Software.
18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
21 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
24 * DEALINGS IN THE SOFTWARE.
26 * @author Kevin Lamonte [kevin.lamonte@gmail.com]
31 import java
.io
.IOException
;
32 import java
.lang
.reflect
.Field
;
33 import java
.text
.MessageFormat
;
34 import java
.util
.ArrayList
;
35 import java
.util
.List
;
37 import java
.util
.ResourceBundle
;
39 import jexer
.bits
.Cell
;
40 import jexer
.bits
.CellAttributes
;
41 import jexer
.event
.TKeypressEvent
;
42 import jexer
.event
.TMenuEvent
;
43 import jexer
.event
.TMouseEvent
;
44 import jexer
.event
.TResizeEvent
;
45 import jexer
.menu
.TMenu
;
46 import jexer
.tterminal
.DisplayLine
;
47 import jexer
.tterminal
.DisplayListener
;
48 import jexer
.tterminal
.ECMA48
;
49 import static jexer
.TKeypress
.*;
52 * TTerminalWindow exposes a ECMA-48 / ANSI X3.64 style terminal in a window.
54 public class TTerminalWindow
extends TScrollableWindow
55 implements DisplayListener
{
61 private static final ResourceBundle i18n
= ResourceBundle
.getBundle(TTerminalWindow
.class.getName());
63 // ------------------------------------------------------------------------
64 // Variables --------------------------------------------------------------
65 // ------------------------------------------------------------------------
70 private ECMA48 emulator
;
73 * The Process created by the shell spawning constructor.
75 private Process shell
;
78 * If true, we are using the ptypipe utility to support dynamic window
79 * resizing. ptypipe is available at
80 * https://gitlab.com/klamonte/ptypipe .
82 private boolean ptypipe
= false;
85 * If true, close the window when the shell exits.
87 private boolean closeOnExit
= false;
89 // ------------------------------------------------------------------------
90 // Constructors -----------------------------------------------------------
91 // ------------------------------------------------------------------------
94 * Public constructor spawns a custom command line.
96 * @param application TApplication that manages this window
97 * @param x column relative to parent
98 * @param y row relative to parent
99 * @param commandLine the command line to execute
101 public TTerminalWindow(final TApplication application
, final int x
,
102 final int y
, final String commandLine
) {
104 this(application
, x
, y
, RESIZABLE
, commandLine
.split("\\s+"),
105 System
.getProperty("jexer.TTerminal.closeOnExit",
106 "false").equals("true"));
110 * Public constructor spawns a custom command line.
112 * @param application TApplication that manages this window
113 * @param x column relative to parent
114 * @param y row relative to parent
115 * @param commandLine the command line to execute
116 * @param closeOnExit if true, close the window when the command exits
118 public TTerminalWindow(final TApplication application
, final int x
,
119 final int y
, final String commandLine
, final boolean closeOnExit
) {
121 this(application
, x
, y
, RESIZABLE
, commandLine
.split("\\s+"),
126 * Public constructor spawns a custom command line.
128 * @param application TApplication that manages this window
129 * @param x column relative to parent
130 * @param y row relative to parent
131 * @param flags mask of CENTERED, MODAL, or RESIZABLE
132 * @param command the command line to execute
134 public TTerminalWindow(final TApplication application
, final int x
,
135 final int y
, final int flags
, final String
[] command
) {
137 this(application
, x
, y
, flags
, command
,
138 System
.getProperty("jexer.TTerminal.closeOnExit",
139 "false").equals("true"));
143 * Public constructor spawns a custom command line.
145 * @param application TApplication that manages this window
146 * @param x column relative to parent
147 * @param y row relative to parent
148 * @param flags mask of CENTERED, MODAL, or RESIZABLE
149 * @param command the command line to execute
150 * @param closeOnExit if true, close the window when the command exits
152 public TTerminalWindow(final TApplication application
, final int x
,
153 final int y
, final int flags
, final String
[] command
,
154 final boolean closeOnExit
) {
156 super(application
, i18n
.getString("windowTitle"), x
, y
,
157 80 + 2, 24 + 2, flags
);
159 this.closeOnExit
= closeOnExit
;
161 String
[] fullCommand
;
163 // Spawn a shell and pass its I/O to the other constructor.
164 if ((System
.getProperty("jexer.TTerminal.ptypipe") != null)
165 && (System
.getProperty("jexer.TTerminal.ptypipe").
169 fullCommand
= new String
[command
.length
+ 1];
170 fullCommand
[0] = "ptypipe";
171 System
.arraycopy(command
, 0, fullCommand
, 1, command
.length
);
172 } else if (System
.getProperty("os.name").startsWith("Windows")) {
173 fullCommand
= new String
[3];
174 fullCommand
[0] = "cmd";
175 fullCommand
[1] = "/c";
176 fullCommand
[2] = stringArrayToString(command
);
177 } else if (System
.getProperty("os.name").startsWith("Mac")) {
178 fullCommand
= new String
[6];
179 fullCommand
[0] = "script";
180 fullCommand
[1] = "-q";
181 fullCommand
[2] = "-F";
182 fullCommand
[3] = "/dev/null";
183 fullCommand
[4] = "-c";
184 fullCommand
[5] = stringArrayToString(command
);
186 // Default: behave like Linux
187 fullCommand
= new String
[5];
188 fullCommand
[0] = "script";
189 fullCommand
[1] = "-fqe";
190 fullCommand
[2] = "/dev/null";
191 fullCommand
[3] = "-c";
192 fullCommand
[4] = stringArrayToString(command
);
194 spawnShell(fullCommand
);
198 * Public constructor spawns a shell.
200 * @param application TApplication that manages this window
201 * @param x column relative to parent
202 * @param y row relative to parent
203 * @param flags mask of CENTERED, MODAL, or RESIZABLE
205 public TTerminalWindow(final TApplication application
, final int x
,
206 final int y
, final int flags
) {
208 this(application
, x
, y
, flags
,
209 System
.getProperty("jexer.TTerminal.closeOnExit",
210 "false").equals("true"));
215 * Public constructor spawns a shell.
217 * @param application TApplication that manages this window
218 * @param x column relative to parent
219 * @param y row relative to parent
220 * @param flags mask of CENTERED, MODAL, or RESIZABLE
221 * @param closeOnExit if true, close the window when the shell exits
223 public TTerminalWindow(final TApplication application
, final int x
,
224 final int y
, final int flags
, final boolean closeOnExit
) {
226 super(application
, i18n
.getString("windowTitle"), x
, y
,
227 80 + 2, 24 + 2, flags
);
229 this.closeOnExit
= closeOnExit
;
231 String cmdShellWindows
= "cmd.exe";
233 // You cannot run a login shell in a bare Process interactively, due
234 // to libc's behavior of buffering when stdin/stdout aren't a tty.
235 // Use 'script' instead to run a shell in a pty. And because BSD and
236 // GNU differ on the '-f' vs '-F' flags, we need two different
238 String cmdShellGNU
= "script -fqe /dev/null";
239 String cmdShellBSD
= "script -q -F /dev/null";
241 // ptypipe is another solution that permits dynamic window resizing.
242 String cmdShellPtypipe
= "ptypipe /bin/bash --login";
244 // Spawn a shell and pass its I/O to the other constructor.
245 if ((System
.getProperty("jexer.TTerminal.ptypipe") != null)
246 && (System
.getProperty("jexer.TTerminal.ptypipe").
250 spawnShell(cmdShellPtypipe
.split("\\s+"));
251 } else if (System
.getProperty("os.name").startsWith("Windows")) {
252 spawnShell(cmdShellWindows
.split("\\s+"));
253 } else if (System
.getProperty("os.name").startsWith("Mac")) {
254 spawnShell(cmdShellBSD
.split("\\s+"));
255 } else if (System
.getProperty("os.name").startsWith("Linux")) {
256 spawnShell(cmdShellGNU
.split("\\s+"));
258 // When all else fails, assume GNU.
259 spawnShell(cmdShellGNU
.split("\\s+"));
263 // ------------------------------------------------------------------------
264 // TScrollableWindow ------------------------------------------------------
265 // ------------------------------------------------------------------------
268 * Draw the display buffer.
272 // Synchronize against the emulator so we don't stomp on its reader
274 synchronized (emulator
) {
276 // Update the scroll bars
279 // Draw the box using my superclass
282 List
<DisplayLine
> scrollback
= emulator
.getScrollbackBuffer();
283 List
<DisplayLine
> display
= emulator
.getDisplayBuffer();
285 // Put together the visible rows
286 int visibleHeight
= getHeight() - 2;
287 int visibleBottom
= scrollback
.size() + display
.size()
288 + getVerticalValue();
289 assert (visibleBottom
>= 0);
291 List
<DisplayLine
> preceedingBlankLines
= new ArrayList
<DisplayLine
>();
292 int visibleTop
= visibleBottom
- visibleHeight
;
293 if (visibleTop
< 0) {
294 for (int i
= visibleTop
; i
< 0; i
++) {
295 preceedingBlankLines
.add(emulator
.getBlankDisplayLine());
299 assert (visibleTop
>= 0);
301 List
<DisplayLine
> displayLines
= new ArrayList
<DisplayLine
>();
302 displayLines
.addAll(scrollback
);
303 displayLines
.addAll(display
);
305 List
<DisplayLine
> visibleLines
= new ArrayList
<DisplayLine
>();
306 visibleLines
.addAll(preceedingBlankLines
);
307 visibleLines
.addAll(displayLines
.subList(visibleTop
,
310 visibleHeight
-= visibleLines
.size();
311 assert (visibleHeight
>= 0);
313 // Now draw the emulator screen
315 for (DisplayLine line
: visibleLines
) {
316 int widthMax
= emulator
.getWidth();
317 if (line
.isDoubleWidth()) {
320 if (widthMax
> getWidth() - 2) {
321 widthMax
= getWidth() - 2;
323 for (int i
= 0; i
< widthMax
; i
++) {
324 Cell ch
= line
.charAt(i
);
325 Cell newCell
= new Cell();
327 boolean reverse
= line
.isReverseColor() ^ ch
.isReverse();
328 newCell
.setReverse(false);
330 if (ch
.getForeColorRGB() < 0) {
331 newCell
.setBackColor(ch
.getForeColor());
332 newCell
.setBackColorRGB(-1);
334 newCell
.setBackColorRGB(ch
.getForeColorRGB());
336 if (ch
.getBackColorRGB() < 0) {
337 newCell
.setForeColor(ch
.getBackColor());
338 newCell
.setForeColorRGB(-1);
340 newCell
.setForeColorRGB(ch
.getBackColorRGB());
343 if (line
.isDoubleWidth()) {
344 putCharXY((i
* 2) + 1, row
, newCell
);
345 putCharXY((i
* 2) + 2, row
, ' ', newCell
);
347 putCharXY(i
+ 1, row
, newCell
);
351 if (row
== getHeight() - 1) {
352 // Don't overwrite the box edge
356 CellAttributes background
= new CellAttributes();
357 // Fill in the blank lines on bottom
358 for (int i
= 0; i
< visibleHeight
; i
++) {
359 hLineXY(1, i
+ row
, getWidth() - 2, ' ', background
);
362 } // synchronized (emulator)
367 * Handle window close.
370 public void onClose() {
373 terminateShellChildProcess();
380 * Handle window/screen resize events.
382 * @param resize resize event
385 public void onResize(final TResizeEvent resize
) {
387 // Synchronize against the emulator so we don't stomp on its reader
389 synchronized (emulator
) {
391 if (resize
.getType() == TResizeEvent
.Type
.WIDGET
) {
392 // Resize the scroll bars
396 // Get out of scrollback
400 emulator
.setWidth(getWidth() - 2);
401 emulator
.setHeight(getHeight() - 2);
403 emulator
.writeRemote("\033[8;" + (getHeight() - 2) + ";" +
404 (getWidth() - 2) + "t");
409 } // synchronized (emulator)
413 * Resize scrollbars for a new width/height.
416 public void reflowData() {
418 // Synchronize against the emulator so we don't stomp on its reader
420 synchronized (emulator
) {
422 // Pull cursor information
425 // Vertical scrollbar
426 setTopValue(getHeight() - 2
427 - (emulator
.getScrollbackBuffer().size()
428 + emulator
.getDisplayBuffer().size()));
429 setVerticalBigChange(getHeight() - 2);
431 } // synchronized (emulator)
437 * @param keypress keystroke event
440 public void onKeypress(final TKeypressEvent keypress
) {
442 // Scrollback up/down
443 if (keypress
.equals(kbShiftPgUp
)
444 || keypress
.equals(kbCtrlPgUp
)
445 || keypress
.equals(kbAltPgUp
)
447 bigVerticalDecrement();
450 if (keypress
.equals(kbShiftPgDn
)
451 || keypress
.equals(kbCtrlPgDn
)
452 || keypress
.equals(kbAltPgDn
)
454 bigVerticalIncrement();
458 // Synchronize against the emulator so we don't stomp on its reader
460 synchronized (emulator
) {
461 if (emulator
.isReading()) {
462 // Get out of scrollback
464 emulator
.keypress(keypress
.getKey());
466 // UGLY HACK TIME! cmd.exe needs CRLF, not just CR, so if
467 // this is kBEnter then also send kbCtrlJ.
468 if (System
.getProperty("os.name").startsWith("Windows")) {
469 if (keypress
.equals(kbEnter
)) {
470 emulator
.keypress(kbCtrlJ
);
479 // Process is closed, honor "normal" TUI keystrokes
480 super.onKeypress(keypress
);
484 * Handle mouse press events.
486 * @param mouse mouse button press event
489 public void onMouseDown(final TMouseEvent mouse
) {
490 if (inWindowMove
|| inWindowResize
) {
491 // TWindow needs to deal with this.
492 super.onMouseDown(mouse
);
496 if (mouse
.isMouseWheelUp()) {
500 if (mouse
.isMouseWheelDown()) {
504 if (mouseOnEmulator(mouse
)) {
505 synchronized (emulator
) {
506 mouse
.setX(mouse
.getX() - 1);
507 mouse
.setY(mouse
.getY() - 1);
508 emulator
.mouse(mouse
);
514 // Emulator didn't consume it, pass it on
515 super.onMouseDown(mouse
);
519 * Handle mouse release events.
521 * @param mouse mouse button release event
524 public void onMouseUp(final TMouseEvent mouse
) {
525 if (inWindowMove
|| inWindowResize
) {
526 // TWindow needs to deal with this.
527 super.onMouseUp(mouse
);
531 if (mouseOnEmulator(mouse
)) {
532 synchronized (emulator
) {
533 mouse
.setX(mouse
.getX() - 1);
534 mouse
.setY(mouse
.getY() - 1);
535 emulator
.mouse(mouse
);
541 // Emulator didn't consume it, pass it on
542 super.onMouseUp(mouse
);
546 * Handle mouse motion events.
548 * @param mouse mouse motion event
551 public void onMouseMotion(final TMouseEvent mouse
) {
552 if (inWindowMove
|| inWindowResize
) {
553 // TWindow needs to deal with this.
554 super.onMouseMotion(mouse
);
558 if (mouseOnEmulator(mouse
)) {
559 synchronized (emulator
) {
560 mouse
.setX(mouse
.getX() - 1);
561 mouse
.setY(mouse
.getY() - 1);
562 emulator
.mouse(mouse
);
568 // Emulator didn't consume it, pass it on
569 super.onMouseMotion(mouse
);
572 // ------------------------------------------------------------------------
573 // TTerminalWindow --------------------------------------------------------
574 // ------------------------------------------------------------------------
577 * Claim the keystrokes the emulator will need.
579 private void addShortcutKeys() {
580 addShortcutKeypress(kbCtrlA
);
581 addShortcutKeypress(kbCtrlB
);
582 addShortcutKeypress(kbCtrlC
);
583 addShortcutKeypress(kbCtrlD
);
584 addShortcutKeypress(kbCtrlE
);
585 addShortcutKeypress(kbCtrlF
);
586 addShortcutKeypress(kbCtrlG
);
587 addShortcutKeypress(kbCtrlH
);
588 addShortcutKeypress(kbCtrlU
);
589 addShortcutKeypress(kbCtrlJ
);
590 addShortcutKeypress(kbCtrlK
);
591 addShortcutKeypress(kbCtrlL
);
592 addShortcutKeypress(kbCtrlM
);
593 addShortcutKeypress(kbCtrlN
);
594 addShortcutKeypress(kbCtrlO
);
595 addShortcutKeypress(kbCtrlP
);
596 addShortcutKeypress(kbCtrlQ
);
597 addShortcutKeypress(kbCtrlR
);
598 addShortcutKeypress(kbCtrlS
);
599 addShortcutKeypress(kbCtrlT
);
600 addShortcutKeypress(kbCtrlU
);
601 addShortcutKeypress(kbCtrlV
);
602 addShortcutKeypress(kbCtrlW
);
603 addShortcutKeypress(kbCtrlX
);
604 addShortcutKeypress(kbCtrlY
);
605 addShortcutKeypress(kbCtrlZ
);
606 addShortcutKeypress(kbF1
);
607 addShortcutKeypress(kbF2
);
608 addShortcutKeypress(kbF3
);
609 addShortcutKeypress(kbF4
);
610 addShortcutKeypress(kbF5
);
611 addShortcutKeypress(kbF6
);
612 addShortcutKeypress(kbF7
);
613 addShortcutKeypress(kbF8
);
614 addShortcutKeypress(kbF9
);
615 addShortcutKeypress(kbF10
);
616 addShortcutKeypress(kbF11
);
617 addShortcutKeypress(kbF12
);
618 addShortcutKeypress(kbAltA
);
619 addShortcutKeypress(kbAltB
);
620 addShortcutKeypress(kbAltC
);
621 addShortcutKeypress(kbAltD
);
622 addShortcutKeypress(kbAltE
);
623 addShortcutKeypress(kbAltF
);
624 addShortcutKeypress(kbAltG
);
625 addShortcutKeypress(kbAltH
);
626 addShortcutKeypress(kbAltU
);
627 addShortcutKeypress(kbAltJ
);
628 addShortcutKeypress(kbAltK
);
629 addShortcutKeypress(kbAltL
);
630 addShortcutKeypress(kbAltM
);
631 addShortcutKeypress(kbAltN
);
632 addShortcutKeypress(kbAltO
);
633 addShortcutKeypress(kbAltP
);
634 addShortcutKeypress(kbAltQ
);
635 addShortcutKeypress(kbAltR
);
636 addShortcutKeypress(kbAltS
);
637 addShortcutKeypress(kbAltT
);
638 addShortcutKeypress(kbAltU
);
639 addShortcutKeypress(kbAltV
);
640 addShortcutKeypress(kbAltW
);
641 addShortcutKeypress(kbAltX
);
642 addShortcutKeypress(kbAltY
);
643 addShortcutKeypress(kbAltZ
);
647 * Convert a string array to a whitespace-separated string.
649 * @param array the string array
650 * @return a single string
652 private String
stringArrayToString(final String
[] array
) {
653 StringBuilder sb
= new StringBuilder(array
[0].length());
654 for (int i
= 0; i
< array
.length
; i
++) {
656 if (i
< array
.length
- 1) {
660 return sb
.toString();
666 * @param command the command line to execute
668 private void spawnShell(final String
[] command
) {
671 System.err.printf("spawnShell(): '%s'\n",
672 stringArrayToString(command));
675 vScroller
= new TVScroller(this, getWidth() - 2, 0, getHeight() - 2);
679 ECMA48
.DeviceType deviceType
= ECMA48
.DeviceType
.XTERM
;
682 ProcessBuilder pb
= new ProcessBuilder(command
);
683 Map
<String
, String
> env
= pb
.environment();
684 env
.put("TERM", ECMA48
.deviceTypeTerm(deviceType
));
685 env
.put("LANG", ECMA48
.deviceTypeLang(deviceType
, "en"));
686 env
.put("COLUMNS", "80");
687 env
.put("LINES", "24");
688 pb
.redirectErrorStream(true);
690 emulator
= new ECMA48(deviceType
, shell
.getInputStream(),
691 shell
.getOutputStream(), this);
692 } catch (IOException e
) {
693 messageBox(i18n
.getString("errorLaunchingShellTitle"),
694 MessageFormat
.format(i18n
.getString("errorLaunchingShellText"),
698 // Setup the scroll bars
699 onResize(new TResizeEvent(TResizeEvent
.Type
.WIDGET
, getWidth(),
702 // Claim the keystrokes the emulator will need.
706 newStatusBar(i18n
.getString("statusBarRunning"));
710 * Terminate the child of the 'script' process used on POSIX. This may
713 private void terminateShellChildProcess() {
715 if (shell
.getClass().getName().equals("java.lang.UNIXProcess")) {
716 /* get the PID on unix/linux systems */
718 Field field
= shell
.getClass().getDeclaredField("pid");
719 field
.setAccessible(true);
720 pid
= field
.getInt(shell
);
721 } catch (Throwable e
) {
722 // SQUASH, this didn't work. Just bail out quietly.
727 // shell.destroy() works successfully at killing this side of
728 // 'script'. But we need to make sure the other side (child
729 // process) is also killed.
730 String
[] cmdKillIt
= {
731 "pkill", "-P", Integer
.toString(pid
)
734 Runtime
.getRuntime().exec(cmdKillIt
);
735 } catch (Throwable e
) {
736 // SQUASH, this didn't work. Just bail out quietly.
743 * Called by emulator when fresh data has come in.
745 public void displayChanged() {
746 getApplication().postEvent(new TMenuEvent(TMenu
.MID_REPAINT
));
750 * Function to call to obtain the display width.
752 * @return the number of columns in the display
754 public int getDisplayWidth() {
756 return getWidth() - 2;
762 * Function to call to obtain the display height.
764 * @return the number of rows in the display
766 public int getDisplayHeight() {
768 return getHeight() - 2;
774 * Hook for subclasses to be notified of the shell termination.
776 public void onShellExit() {
780 getApplication().postEvent(new TMenuEvent(TMenu
.MID_REPAINT
));
784 * Copy out variables from the emulator that TTerminal has to expose on
787 private void readEmulatorState() {
788 // Synchronize against the emulator so we don't stomp on its reader
790 synchronized (emulator
) {
791 setHiddenMouse(emulator
.hasHiddenMousePointer());
793 setCursorX(emulator
.getCursorX() + 1);
794 setCursorY(emulator
.getCursorY() + 1
795 + (getHeight() - 2 - emulator
.getHeight())
796 - getVerticalValue());
797 setCursorVisible(emulator
.isCursorVisible());
798 if (getCursorX() > getWidth() - 2) {
799 setCursorVisible(false);
801 if ((getCursorY() > getHeight() - 2) || (getCursorY() < 0)) {
802 setCursorVisible(false);
804 if (emulator
.getScreenTitle().length() > 0) {
805 // Only update the title if the shell is still alive
807 setTitle(emulator
.getScreenTitle());
811 // Check to see if the shell has died.
812 if (!emulator
.isReading() && (shell
!= null)) {
814 int rc
= shell
.exitValue();
815 // The emulator exited on its own, all is fine
816 setTitle(MessageFormat
.format(i18n
.
817 getString("windowTitleCompleted"), getTitle(), rc
));
820 clearShortcutKeypresses();
821 statusBar
.setText(MessageFormat
.format(i18n
.
822 getString("statusBarCompleted"), rc
));
824 } catch (IllegalThreadStateException e
) {
825 // The emulator thread has exited, but the shell Process
826 // hasn't figured that out yet. Do nothing, we will see
827 // this in a future tick.
829 } else if (emulator
.isReading() && (shell
!= null)) {
830 // The shell might be dead, let's check
832 int rc
= shell
.exitValue();
833 // If we got here, the shell died.
834 setTitle(MessageFormat
.format(i18n
.
835 getString("windowTitleCompleted"), getTitle(), rc
));
838 clearShortcutKeypresses();
839 statusBar
.setText(MessageFormat
.format(i18n
.
840 getString("statusBarCompleted"), rc
));
842 } catch (IllegalThreadStateException e
) {
843 // The shell is still running, do nothing.
847 } // synchronized (emulator)
851 * Check if a mouse press/release/motion event coordinate is over the
854 * @param mouse a mouse-based event
855 * @return whether or not the mouse is on the emulator
857 private final boolean mouseOnEmulator(final TMouseEvent mouse
) {
859 synchronized (emulator
) {
860 if (!emulator
.isReading()) {
865 if ((mouse
.getAbsoluteX() >= getAbsoluteX() + 1)
866 && (mouse
.getAbsoluteX() < getAbsoluteX() + getWidth() - 1)
867 && (mouse
.getAbsoluteY() >= getAbsoluteY() + 1)
868 && (mouse
.getAbsoluteY() < getAbsoluteY() + getHeight() - 1)