Hacking the Codename One Source Code - Part III - Transcript.pdf
1. Hacking the Source - Part III
So we ran the Kitchen Sink as a mobile app using the Simulator and that's great. Before going to Android and iOS lets try running the app as a desktop app. This will be
a relatively simple exercise that can help us understand the more nuanced Android/iOS processes.
2. package com.codename1.demos.kitchen;
import com.codename1.impl.javase.JavaSEPort;
import com.codename1.ui.Display;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
public class KitchenSinkStub implements Runnable {
private static JFrame frm;
private KitchenSink mainApp;
private static final int APP_WIDTH = 800;
private static final int APP_HEIGHT = 600;
public static void main(String[] args) {
JavaSEPort.setNativeTheme("/iOS7Theme.res");
JavaSEPort.blockMonitors();
JavaSEPort.setShowEDTViolationStacks(false);
JavaSEPort.setShowEDTWarnings(false);
JavaSEPort.setAppHomeDir(".KitchenSink");
JavaSEPort.setExposeFilesystem(true);
JavaSEPort.setTablet(true);
JavaSEPort.setUseNativeInput(true);
JavaSEPort.setDefaultPixelMilliRatio(new Double(10));
SwingUtilities.invokeLater(new KitchenSinkStub());
}
KitchenSinkStub (Desktop)
As I mentioned before we don't have a real main class. We only have the bootstrap Codename One main class which we can't run as a standalone app. So we will need
to create a new main class which I will place under a directory called stubs. The term "stub" in Codename One is one we use internally to refer to the OS bootstrap/glue
code. In this case I place the stub under stubs/desktop/src/com/codename1/demos/kitchen.
There is a bit of code here in the stub so I'm going to go over the various lines. It's basically a Swing app that embeds Codename One so you can enhance the stub with
everything available under JavaSE.
I included import statements because they refer to classes in JavaSE and that might be confusing. In the stub it makes sense to import the implementation code since
this code has no portability to other platforms so I’m importing the JavaSEPort directly
3. package com.codename1.demos.kitchen;
import com.codename1.impl.javase.JavaSEPort;
import com.codename1.ui.Display;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
public class KitchenSinkStub implements Runnable {
private static JFrame frm;
private KitchenSink mainApp;
private static final int APP_WIDTH = 800;
private static final int APP_HEIGHT = 600;
public static void main(String[] args) {
JavaSEPort.setNativeTheme("/iOS7Theme.res");
JavaSEPort.blockMonitors();
JavaSEPort.setShowEDTViolationStacks(false);
JavaSEPort.setShowEDTWarnings(false);
JavaSEPort.setAppHomeDir(".KitchenSink");
JavaSEPort.setExposeFilesystem(true);
JavaSEPort.setTablet(true);
JavaSEPort.setUseNativeInput(true);
JavaSEPort.setDefaultPixelMilliRatio(new Double(10));
SwingUtilities.invokeLater(new KitchenSinkStub());
}
KitchenSinkStub (Desktop)
For simplicity I hardcoded the app dimensions but it's pretty easy to create more elaborate heuristics and window positioning using Swing
4. package com.codename1.demos.kitchen;
import com.codename1.impl.javase.JavaSEPort;
import com.codename1.ui.Display;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
public class KitchenSinkStub implements Runnable {
private static JFrame frm;
private KitchenSink mainApp;
private static final int APP_WIDTH = 800;
private static final int APP_HEIGHT = 600;
public static void main(String[] args) {
JavaSEPort.setNativeTheme("/iOS7Theme.res");
JavaSEPort.blockMonitors();
JavaSEPort.setShowEDTViolationStacks(false);
JavaSEPort.setShowEDTWarnings(false);
JavaSEPort.setAppHomeDir(".KitchenSink");
JavaSEPort.setExposeFilesystem(true);
JavaSEPort.setTablet(true);
JavaSEPort.setUseNativeInput(true);
JavaSEPort.setDefaultPixelMilliRatio(new Double(10));
SwingUtilities.invokeLater(new KitchenSinkStub());
}
KitchenSinkStub (Desktop)
If your theme doesn't derive from native this doesn't matter. However, if it does I made it use the iOS 7 theme which is the default iOS theme. This will make the app feel
a bit like an iPad app
5. package com.codename1.demos.kitchen;
import com.codename1.impl.javase.JavaSEPort;
import com.codename1.ui.Display;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
public class KitchenSinkStub implements Runnable {
private static JFrame frm;
private KitchenSink mainApp;
private static final int APP_WIDTH = 800;
private static final int APP_HEIGHT = 600;
public static void main(String[] args) {
JavaSEPort.setNativeTheme("/iOS7Theme.res");
JavaSEPort.blockMonitors();
JavaSEPort.setShowEDTViolationStacks(false);
JavaSEPort.setShowEDTWarnings(false);
JavaSEPort.setAppHomeDir(".KitchenSink");
JavaSEPort.setExposeFilesystem(true);
JavaSEPort.setTablet(true);
JavaSEPort.setUseNativeInput(true);
JavaSEPort.setDefaultPixelMilliRatio(new Double(10));
SwingUtilities.invokeLater(new KitchenSinkStub());
}
KitchenSinkStub (Desktop)
The JavaSEPort is used for the simulator too so things like network monitor and other simulator features might still be on if this is a developer machine. So here we
disable all of those flags
6. package com.codename1.demos.kitchen;
import com.codename1.impl.javase.JavaSEPort;
import com.codename1.ui.Display;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
public class KitchenSinkStub implements Runnable {
private static JFrame frm;
private KitchenSink mainApp;
private static final int APP_WIDTH = 800;
private static final int APP_HEIGHT = 600;
public static void main(String[] args) {
JavaSEPort.setNativeTheme("/iOS7Theme.res");
JavaSEPort.blockMonitors();
JavaSEPort.setShowEDTViolationStacks(false);
JavaSEPort.setShowEDTWarnings(false);
JavaSEPort.setAppHomeDir(".KitchenSink");
JavaSEPort.setExposeFilesystem(true);
JavaSEPort.setTablet(true);
JavaSEPort.setUseNativeInput(true);
JavaSEPort.setDefaultPixelMilliRatio(new Double(10));
SwingUtilities.invokeLater(new KitchenSinkStub());
}
KitchenSinkStub (Desktop)
This is the location for the storage file under the user home directory. In the simulator this is always .cn1 but it makes more sense to use your app name for real world
apps
7. package com.codename1.demos.kitchen;
import com.codename1.impl.javase.JavaSEPort;
import com.codename1.ui.Display;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
public class KitchenSinkStub implements Runnable {
private static JFrame frm;
private KitchenSink mainApp;
private static final int APP_WIDTH = 800;
private static final int APP_HEIGHT = 600;
public static void main(String[] args) {
JavaSEPort.setNativeTheme("/iOS7Theme.res");
JavaSEPort.blockMonitors();
JavaSEPort.setShowEDTViolationStacks(false);
JavaSEPort.setShowEDTWarnings(false);
JavaSEPort.setAppHomeDir(".KitchenSink");
JavaSEPort.setExposeFilesystem(true);
JavaSEPort.setTablet(true);
JavaSEPort.setUseNativeInput(true);
JavaSEPort.setDefaultPixelMilliRatio(new Double(10));
SwingUtilities.invokeLater(new KitchenSinkStub());
}
KitchenSinkStub (Desktop)
The simulator hides the full filesystem and shows a fake one under the .cn1 hierarchy. This disables that behavior so getRoots() will return the filesystems in your
machine. The simulator does that by default to make it feel more like a real device
8. package com.codename1.demos.kitchen;
import com.codename1.impl.javase.JavaSEPort;
import com.codename1.ui.Display;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
public class KitchenSinkStub implements Runnable {
private static JFrame frm;
private KitchenSink mainApp;
private static final int APP_WIDTH = 800;
private static final int APP_HEIGHT = 600;
public static void main(String[] args) {
JavaSEPort.setNativeTheme("/iOS7Theme.res");
JavaSEPort.blockMonitors();
JavaSEPort.setShowEDTViolationStacks(false);
JavaSEPort.setShowEDTWarnings(false);
JavaSEPort.setAppHomeDir(".KitchenSink");
JavaSEPort.setExposeFilesystem(true);
JavaSEPort.setTablet(true);
JavaSEPort.setUseNativeInput(true);
JavaSEPort.setDefaultPixelMilliRatio(new Double(10));
SwingUtilities.invokeLater(new KitchenSinkStub());
}
KitchenSinkStub (Desktop)
When running on a desktop you would prefer showing a tablet UI rather than a phone UI. This means that isTablet & isDesktop would both return true
9. package com.codename1.demos.kitchen;
import com.codename1.impl.javase.JavaSEPort;
import com.codename1.ui.Display;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
public class KitchenSinkStub implements Runnable {
private static JFrame frm;
private KitchenSink mainApp;
private static final int APP_WIDTH = 800;
private static final int APP_HEIGHT = 600;
public static void main(String[] args) {
JavaSEPort.setNativeTheme("/iOS7Theme.res");
JavaSEPort.blockMonitors();
JavaSEPort.setShowEDTViolationStacks(false);
JavaSEPort.setShowEDTWarnings(false);
JavaSEPort.setAppHomeDir(".KitchenSink");
JavaSEPort.setExposeFilesystem(true);
JavaSEPort.setTablet(true);
JavaSEPort.setUseNativeInput(true);
JavaSEPort.setDefaultPixelMilliRatio(new Double(10));
SwingUtilities.invokeLater(new KitchenSinkStub());
}
KitchenSinkStub (Desktop)
Swing is also single threaded... This invokes the run() method on the Swing thread so we can construct the Swing UI
10. SwingUtilities.invokeLater(new KitchenSinkStub());
}
@Override
public void run() {
frm = new JFrame("Kitchen Sink");
Display.init(frm.getContentPane());
Display.getInstance().setProperty("package_name", "com.codename1.demos.kitchen");
Display.getInstance().setProperty("AppName", "KitchenSink");
Display.getInstance().setProperty("AppVersion", "2.0");
Display.getInstance().setProperty("Platform", System.getProperty("os.name"));
Display.getInstance().setProperty("OSVer", System.getProperty("os.version"));
frm.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
java.awt.Dimension d = new java.awt.Dimension(APP_WIDTH, APP_HEIGHT);
frm.getContentPane().setPreferredSize(d);
frm.getContentPane().setMinimumSize(d);
frm.getContentPane().setMaximumSize(d);
frm.pack();
frm.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
Display.getInstance().callSerially(new Runnable() {
@Override
public void run() {
KitchenSinkStub (Desktop)
We create a Swing JFrame and then initialize Codename One with the ContentPane of the frame
11. SwingUtilities.invokeLater(new KitchenSinkStub());
}
@Override
public void run() {
frm = new JFrame("Kitchen Sink");
Display.init(frm.getContentPane());
Display.getInstance().setProperty("package_name", "com.codename1.demos.kitchen");
Display.getInstance().setProperty("AppName", "KitchenSink");
Display.getInstance().setProperty("AppVersion", "2.0");
Display.getInstance().setProperty("Platform", System.getProperty("os.name"));
Display.getInstance().setProperty("OSVer", System.getProperty("os.version"));
frm.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
java.awt.Dimension d = new java.awt.Dimension(APP_WIDTH, APP_HEIGHT);
frm.getContentPane().setPreferredSize(d);
frm.getContentPane().setMinimumSize(d);
frm.getContentPane().setMaximumSize(d);
frm.pack();
frm.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
Display.getInstance().callSerially(new Runnable() {
@Override
public void run() {
KitchenSinkStub (Desktop)
These are display properties we rely on in code, they aren't essential but it's good practice to have them
12. frm.pack();
frm.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
Display.getInstance().callSerially(new Runnable() {
@Override
public void run() {
mainApp.stop();
mainApp.destroy();
Display.getInstance().exitApplication();
}
});
}
} );
Display.getInstance().callSerially(new Runnable() {
@Override
public void run() {
if(Display.getInstance().isEdt()) {
mainApp = new com.codename1.demos.kitchen.KitchenSink();
mainApp.init(this);
mainApp.start();
SwingUtilities.invokeLater(this);
} else {
frm.setVisible(true);
KitchenSinkStub (Desktop)
The frame is sized and positioned. We bind a window listener for the app close event. We make sure to call stop() and destroy() on the main class before exiting. Notice
we invoke these methods on the Codename One thread!
13. @Override
public void run() {
mainApp.stop();
mainApp.destroy();
Display.getInstance().exitApplication();
}
});
}
} );
Display.getInstance().callSerially(new Runnable() {
@Override
public void run() {
if(Display.getInstance().isEdt()) {
mainApp = new com.codename1.demos.kitchen.KitchenSink();
mainApp.init(this);
mainApp.start();
SwingUtilities.invokeLater(this);
} else {
frm.setVisible(true);
}
}
});
}
}
KitchenSinkStub (Desktop)
We go into the Codename One thread to invoke start() & init(). Once those are done we call this method again on the Swing thread and show the frame itself. I took a bit
of a shortcut by reusing the same Runnable instance for both callbacks.
Now that this is out of the way we are nearly done!
14. <target name="desktop" depends="init">
<delete dir="build/tmp" />
<mkdir dir="build/tmp" />
<javac destdir="build/tmp"
encoding="${source.encoding}"
source="1.8"
target="1.8"
classpath="JavaSE.jar">
<src path="${src.dir}:stubs/desktop"/>
</javac>
<copy file="../cn1/Themes/iOS7Theme.res" todir="build/tmp" />
<jar jarfile="kitchesink-desktop.jar">
<manifest> <5>
<attribute name="Main-Class"
value="com.codename1.demos.kitchen.KitchenSinkStub"/>
</manifest>
<fileset dir="../cn1/Ports/JavaSE/build/classes" excludes="*.skin" />
<fileset dir="build/tmp" />
<fileset dir="src" excludes="*.java" />
</jar>
</target>
build.xml
We just need to compile that code and link it with the JavaSEPort code. Since we already have an ant build.xml file this is probably the easiest way to do that. I added a
new target to the build.xml that looks like this.
I want to make sure we are working with a clean slate when running this build
15. <target name="desktop" depends="init">
<delete dir="build/tmp" />
<mkdir dir="build/tmp" />
<javac destdir="build/tmp"
encoding="${source.encoding}"
source="1.8"
target="1.8"
classpath="JavaSE.jar">
<src path="${src.dir}:stubs/desktop"/>
</javac>
<copy file="../cn1/Themes/iOS7Theme.res" todir="build/tmp" />
<jar jarfile="kitchesink-desktop.jar">
<manifest> <5>
<attribute name="Main-Class"
value="com.codename1.demos.kitchen.KitchenSinkStub"/>
</manifest>
<fileset dir="../cn1/Ports/JavaSE/build/classes" excludes="*.skin" />
<fileset dir="build/tmp" />
<fileset dir="src" excludes="*.java" />
</jar>
</target>
build.xml
There are two important things to notice in this statement. I don't use the bootclasspath since the app is now a standard JavaSE app. The classpath points at JavaSE.jar
because I'm compiling against an implementation not an API
16. <target name="desktop" depends="init">
<delete dir="build/tmp" />
<mkdir dir="build/tmp" />
<javac destdir="build/tmp"
encoding="${source.encoding}"
source="1.8"
target="1.8"
classpath="JavaSE.jar">
<src path="${src.dir}:stubs/desktop"/>
</javac>
<copy file="../cn1/Themes/iOS7Theme.res" todir="build/tmp" />
<jar jarfile="kitchesink-desktop.jar">
<manifest> <5>
<attribute name="Main-Class"
value="com.codename1.demos.kitchen.KitchenSinkStub"/>
</manifest>
<fileset dir="../cn1/Ports/JavaSE/build/classes" excludes="*.skin" />
<fileset dir="build/tmp" />
<fileset dir="src" excludes="*.java" />
</jar>
</target>
build.xml
Notice that the stubs/desktop directory is added to the compilation process so that would get bundled in too
17. <target name="desktop" depends="init">
<delete dir="build/tmp" />
<mkdir dir="build/tmp" />
<javac destdir="build/tmp"
encoding="${source.encoding}"
source="1.8"
target="1.8"
classpath="JavaSE.jar">
<src path="${src.dir}:stubs/desktop"/>
</javac>
<copy file="../cn1/Themes/iOS7Theme.res" todir="build/tmp" />
<jar jarfile="kitchesink-desktop.jar">
<manifest> <5>
<attribute name="Main-Class"
value="com.codename1.demos.kitchen.KitchenSinkStub"/>
</manifest>
<fileset dir="../cn1/Ports/JavaSE/build/classes" excludes="*.skin" />
<fileset dir="build/tmp" />
<fileset dir="src" excludes="*.java" />
</jar>
</target>
build.xml
We copy the theme from the Codename One repo so the native theme will look like iOS
18. <target name="desktop" depends="init">
<delete dir="build/tmp" />
<mkdir dir="build/tmp" />
<javac destdir="build/tmp"
encoding="${source.encoding}"
source="1.8"
target="1.8"
classpath="JavaSE.jar">
<src path="${src.dir}:stubs/desktop"/>
</javac>
<copy file="../cn1/Themes/iOS7Theme.res" todir="build/tmp" />
<jar jarfile="kitchesink-desktop.jar">
<manifest> <5>
<attribute name="Main-Class"
value="com.codename1.demos.kitchen.KitchenSinkStub"/>
</manifest>
<fileset dir="../cn1/Ports/JavaSE/build/classes" excludes="*.skin" />
<fileset dir="build/tmp" />
<fileset dir="src" excludes="*.java" />
</jar>
</target>
build.xml
The final step is the executable jar generation. For a jar to be executable we need the main class in the manifest which I define here
19. <target name="desktop" depends="init">
<delete dir="build/tmp" />
<mkdir dir="build/tmp" />
<javac destdir="build/tmp"
encoding="${source.encoding}"
source="1.8"
target="1.8"
classpath="JavaSE.jar">
<src path="${src.dir}:stubs/desktop"/>
</javac>
<copy file="../cn1/Themes/iOS7Theme.res" todir="build/tmp" />
<jar jarfile="kitchesink-desktop.jar">
<manifest> <5>
<attribute name="Main-Class"
value="com.codename1.demos.kitchen.KitchenSinkStub"/>
</manifest>
<fileset dir="../cn1/Ports/JavaSE/build/classes" excludes="*.skin" />
<fileset dir="build/tmp" />
<fileset dir="src" excludes="*.java" />
</jar>
</target>
build.xml
We need 3 things in the jar. First we need the contents of JavaSEPort which is already available in the cn1 project. Notice I exclude the skin file from there as we don't
need the simulator and it will just increase the jar size
20. <target name="desktop" depends="init">
<delete dir="build/tmp" />
<mkdir dir="build/tmp" />
<javac destdir="build/tmp"
encoding="${source.encoding}"
source="1.8"
target="1.8"
classpath="JavaSE.jar">
<src path="${src.dir}:stubs/desktop"/>
</javac>
<copy file="../cn1/Themes/iOS7Theme.res" todir="build/tmp" />
<jar jarfile="kitchesink-desktop.jar">
<manifest> <5>
<attribute name="Main-Class"
value="com.codename1.demos.kitchen.KitchenSinkStub"/>
</manifest>
<fileset dir="../cn1/Ports/JavaSE/build/classes" excludes="*.skin" />
<fileset dir="build/tmp" />
<fileset dir="src" excludes="*.java" />
</jar>
</target>
build.xml
The next thing we need are the compiled sources of the KitchenSink project and KitchenSinkStub
21. <target name="desktop" depends="init">
<delete dir="build/tmp" />
<mkdir dir="build/tmp" />
<javac destdir="build/tmp"
encoding="${source.encoding}"
source="1.8"
target="1.8"
classpath="JavaSE.jar">
<src path="${src.dir}:stubs/desktop"/>
</javac>
<copy file="../cn1/Themes/iOS7Theme.res" todir="build/tmp" />
<jar jarfile="kitchesink-desktop.jar">
<manifest> <5>
<attribute name="Main-Class"
value="com.codename1.demos.kitchen.KitchenSinkStub"/>
</manifest>
<fileset dir="../cn1/Ports/JavaSE/build/classes" excludes="*.skin" />
<fileset dir="build/tmp" />
<fileset dir="src" excludes="*.java" />
</jar>
</target>
build.xml
And finally we need the resources defined within the Kitchen Sink project so I package them directly from the src directory making sure to exclude the Java source files
22. java -jar kitchesink-desktop.jar
Run
After going through all of this I now have a desktop app that I can run using this.
Notice that this is a self contained fat Jar with no external dependencies which means it should work well for tools such as JavaPackager etc.