This article is incomplete
I haven't found any good development guides for Java apps on OSX so I thought I'd write one myself.
I want this article to be both a guide and a reference. I'll try to add/edit info as I learn it through experimentation. If you have any info you think belongs in this article feel free to contact me.
Someone suggested I write a demo app that is fully OS X capable. When I feel like I've aggregated enough information here I will definitely do that.
All of the Apple related classes are located in the com.apple.eawt
package which should come with your Mac JDK.
There quite a few classes in the com.apple.eawt
package but the most important one is Application
. This class lets you get an Application
instance through the static getApplication()
method which represents your app on OSX. Through this instance you'll be able to add all kinds of cool functionality which will make your app feel a lot more native to OSX.
Throughout this article I'll be referencing macApplication
which is simply one such Application
instance:
Application macApplication = Application.getApplication();
So the whole com.apple.eawt
system abstractifies user interaction with OSX specific app components (like menubar items, I'll get to that) as well operations originating from the OS by throwing events. This abstractification is a bit convoluted and took me quite a bit to wrap my head around.
The com.apple.eawt.AppEvent
class contains all the different OSX specific events that might get thrown (in the form of abstract subclasses).
I've separated these events into different categories:
com.apple.eawt.AppEvent.AboutEvent
com.apple.eawt.AppEvent.PreferencesEvent
com.apple.eawt.AppEvent.QuitEvent
com.apple.eawt.AppEvent.AppReOpenedEvent
com.apple.eawt.AppEvent.AppForegroundEvent
com.apple.eawt.AppEvent.AppHiddenEvent
com.apple.eawt.AppEvent.ScreenSleepEvent
com.apple.eawt.AppEvent.SystemSleepEvent
com.apple.eawt.AppEvent.FullScreenEvent
The names of these events should pretty clearly indicate their purpose.
com.apple.eawt.AppEvent.FilesEvent
: I think this is just an abstract class that helps you work with lists of files for stuff
com.apple.eawt.AppEvent.OpenFilesEvent
: Extends FilesEvent
. Not sure where this event would originate fromcom.apple.eawt.AppEvent.PrintFilesEvent
: Extends FilesEvent
. Not sure where this event would originate fromcom.apple.eawt.AppEvent.UserSessionEvent
: I think this is thrown when the computer comes from or goes to the login screencom.apple.eawt.AppEvent.OpenURIEvent
: Not sure how this event is different from OpenFilesEvent
(or where it would originate from)Here's where things get confusing.
Allow me to compare the com.apple.eawt
event system to Swing's event system:
someSwingComponent.addSomeListener(paramSomeEvent)
. On the other hand, com.apple.eawt
doesn't have a uniform way to handle events.com.apple.eawt
provides handlers for some events.
Here are the handlers available:
com.apple.eawt.AboutHandler
com.apple.eawt.PreferencesHandler
com.apple.eawt.OpenFilesHandler
com.apple.eawt.PrintFilesHandler
com.apple.eawt.OpenURIHandler
com.apple.eawt.QuitHandler
Implementing these handlers is pretty straightforward (demonstrated with com.apple.eawt.PreferencesHandler
and com.apple.eawt.AboutHandler
in the next section).
The events that don't have handlers instead have listeners, I'm not sure why... Maybe "handlers" are for user input whereas "listeners" are for OS induced operations? Seems more confusing and redundant than anything else.
Here are the listeners available:
com.apple.eawt.AppReOpenedListener
com.apple.eawt.AppForegroundListener
com.apple.eawt.AppHiddenListener
com.apple.eawt.UserSessionListener
com.apple.eawt.ScreenSleepListener
com.apple.eawt.SystemSleepListener
All these listeners extend com.apple.eawt.AppEventListener
.
Register these listeners through macApplication.addAppEventListener(AppEventListener paramAppEventListener)
.
You can write your own listener that extends com.apple.eawt.AppEventListener
but attempting to register it will result in nothing happening. Only the above 6 listeners can be registered. This should be obvious since the only events you're catching with this system are being thrown by the OS, which means you can't create your own events and therefore have no reason to create your own listeners.
After understanding how the com.apple.eawt
event system works I felt it was only appropriate to quickly write up and share an event adapter that catches everything. I realize it's not very elegant but it "just makes sense".
I'm including it here as well as in the com.moomoohk.Mootilities.OSUtils.MacOSX
package of Mootilities (a pretty handy project of mine that you should totally check out):
import com.apple.eawt.AboutHandler;
import com.apple.eawt.AppEvent.AboutEvent;
import com.apple.eawt.AppEvent.AppForegroundEvent;
import com.apple.eawt.AppEvent.AppHiddenEvent;
import com.apple.eawt.AppEvent.AppReOpenedEvent;
import com.apple.eawt.AppEvent.OpenFilesEvent;
import com.apple.eawt.AppEvent.OpenURIEvent;
import com.apple.eawt.AppEvent.PreferencesEvent;
import com.apple.eawt.AppEvent.PrintFilesEvent;
import com.apple.eawt.AppEvent.QuitEvent;
import com.apple.eawt.AppEvent.ScreenSleepEvent;
import com.apple.eawt.AppEvent.SystemSleepEvent;
import com.apple.eawt.AppEvent.UserSessionEvent;
import com.apple.eawt.AppForegroundListener;
import com.apple.eawt.AppHiddenListener;
import com.apple.eawt.AppReOpenedListener;
import com.apple.eawt.Application;
import com.apple.eawt.OpenFilesHandler;
import com.apple.eawt.OpenURIHandler;
import com.apple.eawt.PreferencesHandler;
import com.apple.eawt.PrintFilesHandler;
import com.apple.eawt.QuitHandler;
import com.apple.eawt.QuitResponse;
import com.apple.eawt.ScreenSleepListener;
import com.apple.eawt.SystemSleepListener;
import com.apple.eawt.UserSessionListener;
/**
* An event adapter meant to catch all the com.apple.eawt events.
*
* @author Meshulam Silk (moomoohk@ymail.com)
* @since Sep 27, 2014
*/
public abstract class MacOSXAppEventAdapter implements AboutHandler, PreferencesHandler, PrintFilesHandler, OpenFilesHandler, OpenURIHandler, QuitHandler, AppReOpenedListener, AppForegroundListener, AppHiddenListener, UserSessionListener, ScreenSleepListener, SystemSleepListener
{
/**
* Constructor
*
* Registers this instance with the com.apple.eawt system
*/
public MacOSXAppEventAdapter()
{
Application.getApplication().addAppEventListener(this);
Application.getApplication().setAboutHandler(this);
Application.getApplication().setPreferencesHandler(this);
Application.getApplication().setPrintFileHandler(this);
Application.getApplication().setOpenFileHandler(this);
Application.getApplication().setOpenURIHandler(this);
Application.getApplication().setQuitHandler(this);
}
@Override
public abstract void systemAboutToSleep(SystemSleepEvent paramSystemSleepEvent);
@Override
public abstract void systemAwoke(SystemSleepEvent paramSystemSleepEvent);
@Override
public abstract void screenAboutToSleep(ScreenSleepEvent paramScreenSleepEvent);
@Override
public abstract void screenAwoke(ScreenSleepEvent paramScreenSleepEvent);
@Override
public abstract void userSessionDeactivated(UserSessionEvent paramUserSessionEvent);
@Override
public abstract void userSessionActivated(UserSessionEvent paramUserSessionEvent);
@Override
public abstract void appHidden(AppHiddenEvent paramAppHiddenEvent);
@Override
public abstract void appUnhidden(AppHiddenEvent paramAppHiddenEvent);
@Override
public abstract void appRaisedToForeground(AppForegroundEvent paramAppForegroundEvent);
@Override
public abstract void appMovedToBackground(AppForegroundEvent paramAppForegroundEvent);
@Override
public abstract void appReOpened(AppReOpenedEvent paramAppReOpenedEvent);
@Override
public abstract void handleQuitRequestWith(QuitEvent arg0, QuitResponse arg1);
@Override
public abstract void openURI(OpenURIEvent paramOpenURIEvent);
@Override
public abstract void openFiles(OpenFilesEvent paramOpenFilesEvent);
@Override
public abstract void handlePreferences(PreferencesEvent paramPreferencesEvent);
@Override
public abstract void printFiles(PrintFilesEvent paramPrintFilesEvent);
@Override
public abstract void handleAbout(AboutEvent paramAboutEvent);
}
The first thing you'll want to do is add and customize the default OSX menu items (About, Preferences, etc.).
It was super confusing to figure out due to all the outdated tutorials and docs I've found online coupled with the fact that a lot of classes are deprecated.
You'll notice that by default your app bundle doesn't have a "Preferences" item in the main app menu. I assume this is because most people won't implement their own preferences implementation. Most (if not all) of the guides I came across online state that you should call macApplication.setEnabledPreferencesMenu(true);
or macApplication.addPreferencesMenuItem()
to get the item to show up. However, these methods are deprecated and I therefore recommend you don't use them.
To get the "Preferences" item to show up all you seemingly need to do is implement a handler for that item, like so:
macApplication.setPreferencesHandler(new PreferencesHandler()
{
@Override
public void handlePreferences(PreferencesEvent paramPreferencesEvent)
{
// Make preferences window appear here
}
});
This appears to be applicable to the "About" menu item as well:
macApplication.setAboutHandler(new AboutHandler()
{
@Override
public void handleAbout(AboutEvent paramAboutEvent)
{
// Make about window appear here
}
});
This code should replace macApplication.setEnabledAboutMenuItem(true);
and macApplication.addAboutMenuItem();
.
In Mac OS X Lion (10.7) Apple introduced full screen apps.
Calling FullScreenUtilities.setWindowCanFullScreen(frame, true);
, where frame
is your window object (as in, extends java.awt.Window
. This includes javax.swing.JFrame
), will enable this functionality.
Before enabling full screen:
After enabling full screen:
To programmatically toggle full screen mode call macApplication.requestToggleFullScreen(frame);
. There's no way to set the fullscreen state manually, only a toggle.
I'm not sure how com.apple.eawt
handles these calls on systems older than 10.7. Here's a more complete example with a (possibly redundant) check to make sure the system is at least Mac OS X Lion:
if (System.getProperty("os.name").startsWith("Mac OS X"))
{
String osVer = System.getProperty("os.version");
int major = Integer.parseInt(osVer.substring(0, osVer.indexOf(".")));
osVer = osVer.substring(osVer.indexOf(".") + 1);
int minor = Integer.parseInt(osVer.substring(0, osVer.indexOf(".")));
if (major >= 10 && minor >= 7)
{
FullScreenUtilities.setWindowCanFullScreen(frame, true);
macApplication.requestToggleFullScreen(frame);
}
}
com.apple.eawt
does not provide any way to send NC notifications.
Apple implemented NC in Mountain Lion (10.8), one version after Lion (10.7). Since com.apple.eawt
includes full screen capabilities I can only assume that the package hasn't been updated since Lion was a thing, which would be 2011. This might explain why there's no NC capabilities in com.apple.eawt
. Here's to hoping that gets patched in before long.
Unfortunately I haven't tried most of these solutions so I can't really provide much more than information I've found online (for now).
AppleScript: Someone suggested executing an AppleScript command that produces a notification.
The "display notification" command will do exactly that.
Optional parameters for the command:
Here's an example:
display notification "Message" with title "Title" subtitle "Subtitle" sound name "Glass"
will produce this (while playing /System/Library/Sounds/Glass.aiff):
That's the full extent of what you can achieve with the command. You can't set the app icon or create interactive notifications.
There are multiple way to run this AppleScript from within Java:
Runtime.exec
Runtime.getRuntime().exec(new String[] { "osascript", "-e", "display notification "Message" with title "Title" subtitle "Subtitle" sound name "Glass"" });
ProcessBuilder
new ProcessBuilder("osascript", "-e", "display notification "Message" with title "Title" subtitle "Subtitle" sound name "Glass"").start();
ScriptEngine
According this article by Apple the correct way to run AppleScript from Java is with javax.script.ScriptEngine
.
I'm on OS X Mavericks (10.9) and I tried this solution with both Java 1.6 and 1.7 but neither worked.
new ScriptEngineManager().getEngineByName("AppleScript").eval("display notification "Message" with title "Title" subtitle "Subtitle" sound name "Glass"");
So the upside to this AppleScript approach is that it's fully vanilla Java, the downside is that you're extremely limited with what you can produce.
Growl: I don't really like Growl and wouldn't use it if I had the choice but it seems like a pretty viable solution, especially since it lets users set whether Growl notifications get forwarded to NC. That's as close as you'll get without some weird hack (coming up next).
Here are two libraries that provide Java bindings for Growl (found on this page):
After a short googling I found this sample class for LibGrowl that should help get you started with it.
"Using JNA to manipulate NSObjects": I put that in quotes because I have close to no idea what it actually means.
This solution relies on writing an Objective-C library and using it through JNA (Java Native Access). The concept sounds simple enough but for someone with no Objective-C/Cocoa experience is probably impossible to implement.
Lots of people on Stack Overflow seemed to know what they were doing with this and got some systems working. Here are some threads for extended reading:
wokier/java-to-OS-notify: This library relies on and communicates with CLI tools. It covers more than just NC on OS X.
For our purposes this library seems like a last resort (multiple dependencies and lots of overhead).
Ideally we shouldn't have to go through all this trouble.
This following approach doesn't seem to work with later combinations of Java and OS X. I'm not sure whether it's the versions of Java I tried with (1.6/1.7) or my OS (Mavericks). I can't find a way to set the menu bar title on my setup.
All the guides that I came across stated that you have to set the com.apple.mrj.application.apple.menu.about.name
property to the name of your app and it should work.
Now that will indeed work right off the bat if you set the property through command line, but if you're like me you'll be wanting to do it programmatically:
System.setProperty("com.apple.mrj.application.apple.menu.about.name", "This is my app name!");
Here's where it gets quirky: Apparently this won't work if you make the call after you either interact with java.awt.Toolkit
or set your app's look and feel.
So just make the call first thing in the main right? Not quite. Interacting with java.awt.Toolkit
includes loading a class that extends javax.swing.JFrame
. This means that if your main method is in a subclass of javax.swing.JFrame
it won't work (since the class is loaded right away)!
To remedy this simply extract your main method to a separate class and you're good to go:
public class Launcher
{
public static void main(String[] args)
{
System.setProperty("com.apple.mrj.application.apple.menu.about.name", "This is my app");
MyJFrame frame = new MyJFrame();
frame.setVisible(true);
}
}
I also found mention of setting the -Xdock:name
nonstandard JVM flag to your title:
-Xdock:name="This is my app"
You can set this through the "VM Options" section when exporting your Mac Application Bundle in Eclipse.
This approach doesn't appear to work either as a result of a bug with Java. It has been reported here and here
Effectively, there currently appears to be no way to set the menu bar title in later versions of OS X coupled with later versions of Java.
Multitouch gestures are what I've been missing in my Java apps all these years.
Luckily the com.apple.eawt.event
package provides a system to handle gestures.
Unluckily it doesn't appear to work. As mentioned above, I'm on 10.9.5 and I've tested these features on 10.7.5.
Events to listen for:
com.apple.eawt.event.GesturePhaseEvent
: Generic prolonged event that has a start point and end pointcom.apple.eawt.event.MagnificationEvent
: Two finger pinchcom.apple.eawt.event.RotationEvent
: Two finger rotatecom.apple.eawt.event.SwipeEvent
: Two (possibly three) finger swipeListeners to use:
com.apple.eawt.event.GesturePhaseListener
com.apple.eawt.event.MagnificationListener
com.apple.eawt.event.RotationListener
com.apple.eawt.event.SwipeListener
All 4 listeners implement the com.apple.eawt.event.GestureListener
interface.
Listeners are attached to javax.swing.JComponent
s through the com.apple.eawt.event.GestureUtilities.addGestureListenerTo(javax.swing.JComponent component, com.apple.eawt.event.GestureListener listener)
method
Here's an example implementation of all these event listeners:
import javax.swing.JPanel;
import com.apple.eawt.event.GesturePhaseEvent;
import com.apple.eawt.event.GesturePhaseListener;
import com.apple.eawt.event.GestureUtilities;
import com.apple.eawt.event.MagnificationEvent;
import com.apple.eawt.event.MagnificationListener;
import com.apple.eawt.event.RotationEvent;
import com.apple.eawt.event.RotationListener;
import com.apple.eawt.event.SwipeEvent;
import com.apple.eawt.event.SwipeListener;
public class GestureListenerTest
{
JPanel p = new JPanel();
public static void main(String[] args)
{
GestureUtilities.addGestureListenerTo(p, new SwipeListener()
{
@Override
public void swipedDown(SwipeEvent e)
{
System.out.println("Down");
}
@Override
public void swipedLeft(SwipeEvent e)
{
System.out.println("Left");
}
@Override
public void swipedRight(SwipeEvent e)
{
System.out.println("Right");
}
@Override
public void swipedUp(SwipeEvent e)
{
System.out.println("Up");
}
});
GestureUtilities.addGestureListenerTo(p, new MagnificationListener()
{
@Override
public void magnify(MagnificationEvent e)
{
System.out.println(e.getMagnification());
}
});
GestureUtilities.addGestureListenerTo(p, new RotationListener()
{
@Override
public void rotate(RotationEvent e)
{
System.out.println(e.getRotation());
}
});
GestureUtilities.addGestureListenerTo(p, new GesturePhaseListener()
{
@Override
public void gestureBegan(GesturePhaseEvent e)
{
System.out.println("Begin");
}
@Override
public void gestureEnded(GesturePhaseEvent e)
{
System.out.println("End");
}
});
}
}
The com.apple.eawt.event.GestureAdapter
class is an adapter for all the possible gesture events.
Again, I haven't been able to get any of these events to fire on my setup.
In Mac OS X Lion (10.7) Apple introduced auto hiding scroll bars.
Unfortunately this scroll bar style isn't available out of the box (even with the OS X Look and Feel set).
I did come across this workaround a while ago but I haven't tested it extensively.
The only generated JavaDocs I've found online can be found here.
Source code (with inline JavaDocs) can be found here.
When I get the chance I'll see if I can regenerate JavaDocs from the source and place them on my site in case CodeRanch decides to stop hosting it.
Throughout the article I've tried to keep my personal opinions on the com.apple.eawt
package bottled up but I feel like some aspects of it that I find too problematic to ignore.
It's a (seemingly) inconsistent system
As noted above in the "Events" section, the package handles different types of events in different ways. Some require a "handler" while others require a "listener". This makes absolutely no sense and just creates confusion.
I say "seemingly" because there is probably some logic behind this design decision. I proposed a possibility above ("handlers" = user input, "listener" = OS operations), another could be that this is a carry over from the Objective-C/Cocoa system. After all, I did notice that a lot of the internal classes in com.apple.eawt
follow a lot of naming and coding conventions that aren't very Java.
It's old
I'm sure the exact age of the package can be found online but above I estimated it to be around 3 years old. Since then there have been 2 new iterations of OS X (10.8 Mountain Lion and 10.9 Mavericks, 10.10 Yosemite's public release is right around the corner) and new native features have been added (like Notification Center). Without an update to this package none of these recent features are easily accessible which is a super bummer.