Java – We have the Technology

ShowRunnerOnLinux_2020-04-20 13-36-24
Java App running on Linux

We recently proved that Java desktop programs can be easily deployed to Windows and Linux. Now we have proved that other complex parts of the program to run a series of Impress Shows in a loop.

  1. Running the command line to launch the Impress program to show the slide show.
  2. Finding out when the show is complete.
  3. Clicking the mouse repeatedly so that the show ends when it finally reaches the black screen.

1 and 2 are pretty easy. In Qt there is a class that executes a command in another process. And you can wait on it or check to see if it is complete. We can’t wait of course since Impress just waits forever for a click on a black screen at the end. Soooo.

3 is not so easy on Qt. The solution differs by OS. On windows you need to call the Kernel to send events to be dispatched through the event handler. UINPUT calls. On Linux, the easiest solution I found was to launch the “xtodo click 1” command to perform a mouse click. The xtodo command is easily installed on Linux, but this is another step to make the program work. But the Java solution is much easier.

Java Solution to Problems

In java, there is of course a class to launch programs. So executing a command in another process is no problem. However there is a little bit of funky business when it comes to checking to see if the process is complete. To check, you don’t do a wait for a short time and see if it timed out, but instead, you check for the Exit Code and if you get an exception, then the process is not complete. Odd. But it works. See the code gist below.

But the Mouse click problem is really simple, and works across OSs. Just use the Robot Class. The Robot class clicks the mouse and a variety of other things. So no kernel calls or launching other utilities that need to be installed. It just works. Same code everywhere.

So here is the example program running on Windows 10, on the left and Linux Mint 19 on a 4K monitor on the right. The example is in the center with the Qt version of the app to the left for comparison. The example just launches one show, clicks the mouse every 5 seconds and senses when the show has exited. Here are some gists of the code:

Timer Code


//
// a timer task to tick down the time
// up the tics and click the mouse
//
private class MyTimerTask extends TimerTask {
@Override
public void run()
{
nTimerTics++;
tfTimerTics.setText(String.valueOf(nTimerTics));
// try to click the mouse here
try {
Robot bot = new Robot();
int mask = InputEvent.BUTTON1_DOWN_MASK;
// don't move the mouse in case the user wants to click on Stop Show or
// something else. It will be fine, the show will stop on the click
// if it's at the end.
//bot.mouseMove(100, 100);
bot.mousePress(mask);
bot.mouseRelease(mask);
try {
// hang for a bit before release
Thread.sleep(100);
}
catch(InterruptedException ex) {
Thread.currentThread().interrupt();
System.out.println("MyTimerTask interrupted");
}
bot.mouseRelease(mask);
}
catch (Exception e ) {
System.out.println("MyTimerTask Robot exception");
}
// if the show is running, watch for it to end in a strange way
if ( bShowRunning ) {
try {
// so, rather than a wait, we check for exit value
// and if that tosses an exception, the process is
// still running. Ooooooookkkkkaaaaaayyyyyy No Problem
int exitValue = pShowProcess.exitValue();
// guess we don't do this to get rid of the not referened warning
//(void)exitValue;
// we don't care what the exit value was
exitValue = 0;
// but if we get here, then the show stopped, so
// if we stop it now, it won't need to wait, it will be fine
// we think.
stopShow();
} catch (Exception ex) {
// Process is still running. So just keep going until
// mouse clicks or something else stops the show
}
}
}
}
public void startTimer( long msecsPerTic ) {
try {
if ( bTimerRunning ) {
System.out.println("startTimer already running" );
return;
}
aTimer = new Timer();
timerTask = new MyTimerTask();
aTimer.schedule(timerTask, msecsPerTic, msecsPerTic);
bTimerRunning = true;
System.out.println("startTimer started for "+String.valueOf(msecsPerTic) );
} catch (Exception ex)
{
// just ignore any exceptions
System.out.println("startTimer exception" );
}
}
public void stopTimer() {
try {
if ( !bTimerRunning ) {
System.out.println("stopTimer not running" );
return;
}
timerTask.cancel();
bTimerRunning = false;
System.out.println("stopTimer cancelled" );
} catch (Exception ex) {
System.out.println("stopTimer exception" );
}
}

Start / Stop Impress Slide Shows

Here’s the code to start and stop the slide shows that uses the timer code above.


public void startShowPlaying( String sImpress, String sOptions, String sShowPath ) {
if ( bShowRunning ) {
System.out.println("startShowPlaying already running");
return;
}
String cmdString = sImpress +" "+sOptions+" "+sShowPath;
try {
pShowProcess = Runtime.getRuntime().exec( cmdString );
System.out.println("startShowPlaying show started");
bShowRunning = true;
if ( bTimerRunning ) {
stopTimer();
}
startTimer( 5000 );
} catch (Exception ex ) {
System.out.println("startShowPlaying exception");
}
}
public void stopShow() {
if ( !bShowRunning ) {
System.out.println("stopShow show not running");
return;
}
// we cannot use destroy() since that would leave the Impress show
// in a bad state. So all we can do is wait on the user to stop the
// show and then clean up.
try {
// stop the mouse clicks
stopTimer();
System.out.println("stopShow waiting for you to stop the show");
pShowProcess.waitFor();
System.out.println("stopShow show not running");
bShowRunning = false;
} catch (Exception ex ) {
System.out.println("stopShow exception");
bShowRunning = false; // try to clean up
}
}

Maybe these will help you in your solutions.

The code for the complete app will appear here on GitHub and on my SourceForge page. Note I’ll probably change the name of the Repo at some point, when I figure out how.

May you walk in the light of the Crystal.
:ww

Update: April 23, 2020

I’ve been struggling with getting the app to work on Linux. The program would not launch slide show files with Impress, or anything else including the text editor xed.

ImpressShowRunner 2020-04-23 22-00-03.png

After quite a bit of work, and lots of searches, I found a reference to an Eclipse bug [ Java IDE ] where the Runtime.exec() method failed. After running the program as a jar file on Linux, it turns out that an Eclipse bug is the problem. The program launches shows reliably. There are remaining issues with spaces, dashes and parenthesis in paths to the show files. These problems are far more tractable than the runtime error.

I’ll be posting new versions and details here on the SourceForge Siteand the sources are here on GitHub. The repository name will probably change soon.

:ww