Subscribe to Grim's Projects        RSS Feed
-----

Java OS sorta? (cont.)

Icon 9 Comments
Continuation of: Java OS sorta?

Been a while since I published anything so I figured I would post what I was up to in computer land.

I since migrated my comp from TinyCoreLinux to ArchLinux. I really like the portability of TinyCoreLinux. However, I had trouble using a video driver other than the default vesa driver and I also wanted to learn more about Linux. I uncovered ArchBang Linux and thankfully its maintainer also provides a tutorial on how to create your very own customized version.

Using a combination of the beginner's guide on the ArchLinux website and the ArchBang website I successfully installed ArchLinux on my "trusty" Dell. It took a couple of attempts and modifying the kernel line on the GRUB boot screen but now I'm using the intel driver for the 845g chipset on the laptop (works 99% of the time). The 1% annoyance is that sometimes on bootup the screen will turn off so I have to log in blindly into my desktop/window manager and hit a key binding to shut off/turn on the LCD once more using xrandr which powers up the display. The alternative is physically cycling the laptop again, which I dislike doing.

So how does this relate to the JavaOS? Well it's the same idea I had in TinyCoreLinux in terms of setting up a "Java exclusive" environment. It's not really exclusive in the sense that ArchLinux is the actual operating system running behind the scenes, but after going through various window managers (TWM, OpenBox, JWM, and TinyWM to name a few) I realized I wanted to understand what exactly it was the window manager did/does. The name makes it obvious but it wasn't until I modified my .xinitrc file and typed startx in the console prompt that I really understood what was going on, and then I would say I had an aha Linux moment.

The best part about it was realizing I could run singular applications without all the other stuff to slow it down. So I thought... could I run a Java program that would take up the whole screen? Not really a JavaOS, but more of a Java window manager that has the ability to run other Java applications?

I looked at a ScreenManager class supplied by David Brackeen of brackeen.com as supplied in the source code of a Java game programming book he authored. Modified it a bit to catch/handle some exceptions he seemed to ignore and allow the ability to supply your own custom Swing JFrame rather than use the JFrame as a giant canvas. Finally, I modified another bit of his source code to actually make use of the ScreenManager class and instantiate a virtual desktop. The only real elegance it provides is that what would be considered the launchbar/start menu/taskbar/whatever other name it takes on is a JMenuBar which fits nicely into a JFrame by default, the content pane of the JFrame is a JTabbedPane which allows me to create multiple tabs (I allowed a maximum of 10 so as to be able to use a mnemonic to switch between each using the number keys and Alt combination). Each of the tabs created then gets populated with a JDesktopPane, which would allow you to create "applications" using JInternalFrames. This encapsulates the handling of windows/desktops in Java.

Here's the code:

ScreenManager.java
import java.awt.*;
import java.awt.image.BufferStrategy;
import java.awt.image.BufferedImage;
import javax.swing.JFrame;

/**
	The ScreenManager class manages initializing and displaying
	full screen graphics modes.
 */
public class ScreenManager 
{

	private GraphicsDevice device;
	
	/**
		Creates a new ScreenManager object.
	 */
	public ScreenManager()
	{
		GraphicsEnvironment environment =
				GraphicsEnvironment.getLocalGraphicsEnvironment();
		try
		{
			device = environment.getDefaultScreenDevice();
		}
		catch(HeadlessException ex)
		{
			System.err.println("Current environment does not support display/keyboard/mouse.");
			ex.printStackTrace();
			System.exit(-1);
		}
	}
	
	/**
		Returns a list of compatible display modes for the
		default device on the system.
	 */
	public DisplayMode[] getCompatibleDisplayModes()
	{
		return device.getDisplayModes();
	}
	
	/**
		Returns the first compatible mode in a list of modes.
		Returns null if no modes are compatible.
	 */
	public DisplayMode findFirstCompatibleMode(
			DisplayMode modes[])
	{
		DisplayMode goodModes[] = device.getDisplayModes();
		for(int i = 0; i < modes.length; i++)
		{
			for(int j = 0; j < goodModes.length; j++)
			{
				if(displayModesMatch(modes[i], goodModes[j]))
				{
					return modes[i];
				}
			}
		}
		
		return null;
	}
	
	/**
		Returns the current display mode.
	 */
	public DisplayMode getCurrentDisplayMode()
	{
		return device.getDisplayMode();
	}
	
	/**
		Determines if two display modes "match". Two display
		modes match if they have the same resolution, bit depth,
		and refresh rate. The bit depth is ignored if one of the
		modes has a bit depth of DisplayMode.BIT_DEPTH_MULTI.
		Likewise, the refresh rate is ignored if one of the
		modes has a refresh rate of
		DisplayMode.REFRESH_RATE_UNKNOWN.
	 */
	public boolean displayModesMatch(DisplayMode modeA,
			DisplayMode modeB)
	{
		if(modeA.getWidth() != modeB.getWidth() ||
				modeA.getHeight() != modeB.getHeight())
		{
			return false;
		}
		
		if(modeA.getBitDepth() != DisplayMode.BIT_DEPTH_MULTI &&
				modeB.getBitDepth() != DisplayMode.BIT_DEPTH_MULTI &&
				modeA.getBitDepth() != modeB.getBitDepth())
		{
			return false;
		}
		
		if(modeA.getRefreshRate() != DisplayMode.REFRESH_RATE_UNKNOWN &&
				modeB.getRefreshRate() != DisplayMode.REFRESH_RATE_UNKNOWN &&
				modeA.getRefreshRate() != modeB.getRefreshRate())
		{
			return false;
		}
		
		return true;
	}
	
	/**
 		Enters full screen mode and changes the display mode.
 		If the specified display mode is null or not compatible
 		with this device, or if the display mode cannot be
 		changed on this system, the current display mode is used.
 		
 		The display uses a BufferStrategy with 2 buffers.
	 */
	public void setFullScreen(JFrame frame, DisplayMode displayMode)
	{
		frame.setUndecorated(true); // IllegalComponentStateException cannot (SHOULD NOT) occur upon creation JFrame automatically meets requirements
		frame.setIgnoreRepaint(true);
		frame.setResizable(false);
		
		if(device.isFullScreenSupported())
		{
			device.setFullScreenWindow(frame);
			if(displayMode != null &&
					device.isDisplayChangeSupported())
			{
				try
				{
					device.setDisplayMode(displayMode);
				}
				catch(IllegalArgumentException ex)
				{
					// DisplayMode dm was null or is not a valid mode according to environment
				}
			}
			try{
				frame.createBufferStrategy(2);	// IllegalArgumentException cannot (SHOULD NOT) occur as int numBuffers > 0
			}
			catch(IllegalStateException ex)
			{
				System.err.println("Frame could not be double buffered.");
				ex.printStackTrace();
				System.exit(-1);
			}
		}
	}
	
	/**
		Gets the graphics context for the display. The
		ScreenManager uses double buffering, so applications must
		call update() to show any graphics drawn.
		
		The application must dispose of the graphics object.
	 */
	public Graphics2D getGraphics(){
		Window window = device.getFullScreenWindow();
		if(window != null)	// null window would imply there isn't a full screen window available
		{
			BufferStrategy strategy = window.getBufferStrategy();
			return (Graphics2D)strategy.getDrawGraphics();
		}
		else
		{
			return null;
		}
	}
	
	/**
		Updates the display.
	 */
	public void update()
	{
		Window window = device.getFullScreenWindow();
		if(window != null)
		{
			BufferStrategy strategy = window.getBufferStrategy();
			if(!strategy.contentsLost()){
				strategy.show();
			}
		}
		// Sync the display on some systems.
		// (on Linux, this fixes event queue problems)
		try
		{
			Toolkit.getDefaultToolkit().sync();
		}catch(AWTError ex)
		{
			System.err.println("A toolkit could not be found/accessed/instantiated");
			ex.printStackTrace();
			System.exit(-1);
		}
	}
	
	/**
		Returns the window currently used in full screen mode.
		Returns null if the device is not in full screen mode.
	 */
	public Window getFullScreenWindow(){
		return device.getFullScreenWindow();
	}
	
	/**
		Returns the width of the window currently used in full
		screen mode. Returns 0 if the device is not in full
		screen mode.
	 */
	public int getWidth(){
		Window window = device.getFullScreenWindow();
		if(window != null)
		{
			return window.getWidth();
		}
		else
		{
			return 0;
		}
	}
	
	/**
		Returns the height of the window currently used in full
		screen mode. Returns 0 if the device is not in full
		screen mode.
	 */
	public int getHeight(){
		Window window = device.getFullScreenWindow();
		if(window != null)
		{
			return window.getHeight();
		}
		else
		{
			return 0;
		}	
	}
	
	/**
		Restores the screen's display mode.
	 */
	public void restoreScreen(){
		Window window = device.getFullScreenWindow();
		if(window != null){
			window.dispose();
		}
		device.setFullScreenWindow(null);
	}
	
	/**
		Creates an image compatible with the current display.
	 */
	public BufferedImage createCompatibleImage(int w, int h, 
			int transparency)
	{
		Window window = device.getFullScreenWindow();
		if(window != null)
		{
			GraphicsConfiguration gc =
					window.getGraphicsConfiguration();
			return gc.createCompatibleImage(w, h, transparency);
		}
		return null;
	}
}


VirtualDesktop.java
import java.awt.*;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;

import javax.swing.JFrame;
import javax.swing.JMenuBar;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.JSeparator;
import javax.swing.JDesktopPane;
import javax.swing.KeyStroke;
import javax.swing.JTabbedPane;
// import javax.swing.JLabel;


public class VirtualDesktop {

    public static void main(String args[]) {
        VirtualDesktop virtualDesktop = new VirtualDesktop();
        virtualDesktop.run();
    }

    private static final DisplayMode POSSIBLE_MODES[] = {
        new DisplayMode(800, 600, 32, 0),
        new DisplayMode(800, 600, 24, 0),
        new DisplayMode(800, 600, 16, 0),
        new DisplayMode(640, 480, 32, 0),
        new DisplayMode(640, 480, 24, 0),
        new DisplayMode(640, 480, 16, 0)
    };
    
    private static final byte MAX_DESKTOPS = 10;
    private JTabbedPane tabbedDesktops;

    private ScreenManager screen;

    public void run() {
    	screen = new ScreenManager();
        DisplayMode displayMode =
        		screen.findFirstCompatibleMode(POSSIBLE_MODES);
        screen.setFullScreen(createDesktop(), displayMode);
        render();
    }


    public void render()
    {
        Graphics2D g = screen.getGraphics();
        draw(g);
        g.dispose();
        screen.update();
    }
    
    public void draw(Graphics2D g) {
   		screen.getFullScreenWindow().paintComponents(g);
    }
    
    public JFrame createDesktop(){
    	JFrame frame = new JFrame();
    	
    	// Create initial desktop
    	tabbedDesktops = new JTabbedPane(JTabbedPane.BOTTOM);
		
		tabbedDesktops.addTab("Desktop " + tabbedDesktops.getTabCount(), new JDesktopPane());
		tabbedDesktops.setMnemonicAt(0, 
				KeyEvent.getExtendedKeyCodeForChar(KeyEvent.VK_0));
		
		frame.setContentPane(tabbedDesktops);
    	
		// Create system menu
    	JMenuBar menuBar = new JMenuBar();
		
		JMenu menu = new JMenu("JavaWM");
		menu.setMnemonic(
				KeyEvent.getExtendedKeyCodeForChar(KeyEvent.VK_J));
		
		
		// Desktops sub menu
		JMenu subMenu = new JMenu("Desktops");
		
		JMenuItem menuItem = new JMenuItem("Add");
		
		ActionListener menuListener = new ActionListener() {
			public void actionPerformed(ActionEvent e){
				if(tabbedDesktops.getTabCount() < MAX_DESKTOPS)
				{
					byte tabCount = (byte)tabbedDesktops.getTabCount();
					tabbedDesktops.addTab("Desktop " + tabCount, new JDesktopPane());
					tabbedDesktops.setMnemonicAt(tabCount, KeyEvent.VK_0 + tabCount);
				}
			}
		};
		menuItem.addActionListener(menuListener);
		
		subMenu.add(menuItem);
		
		menuItem = new JMenuItem("Remove");
		
		menuListener = new ActionListener() {
			public void actionPerformed(ActionEvent e){
				if(tabbedDesktops.getTabCount() > 1)
				{
					tabbedDesktops.removeTabAt(tabbedDesktops.getTabCount() - 1);
				}
			}
		};
		menuItem.addActionListener(menuListener);
		
		subMenu.add(menuItem);
		subMenu.add(new JSeparator());
		
		menu.add(subMenu);
		menu.add(new JSeparator());
		
		// Exit JavaWM
		menuItem = new JMenuItem("Exit");
		menuItem.setAccelerator(KeyStroke.getKeyStroke(
				KeyEvent.VK_END,
				InputEvent.CTRL_DOWN_MASK | InputEvent.ALT_DOWN_MASK));
		
		menuListener = new ActionListener() {
			public void actionPerformed(ActionEvent e){
				screen.restoreScreen();
			}
		};
		menuItem.addActionListener(menuListener);
		
		menu.add(menuItem);
		menuBar.add(menu);
		
		frame.setJMenuBar(menuBar);
		
		return frame;
    }
}


And of course the mandatory screenshots (I have it running Nimbus theme by default, sue me for liking pretty):

How it looks on launch
Attached Image

The menu options to add/remove desktops and exit
Attached Image

The maximum number of desktops
Attached Image

My .xinitrc file has a the following line:
exec nice -n -10 java ...
Where ... represents the -cp classpath and classname (VirtualDesktop).

This gives the JVM a higher priority and the only other processes (for my unique user) running are startx, xinit, dbus-launch and dbus-daemon. I would make a wild guess that the JVM doesn't really interact with dbus so I might be able to get away with disabling it. All other processes are root ArchLinux processes, such as network setup and the like.

I'm not skilled enough to really make a JavaWM this is more of a "hey neato, wonder where this could go?" idea. This is not something that is within the realm of possibility for me given what I know about programming and what I know about OS concepts (which boils down to nothing, in terms of programming). It would be great though if it might inspire a Java guru on here to actualize the concept, so here's to dreaming...

9 Comments On This Entry

Page 1 of 1

grimpirate Icon

18 December 2011 - 08:53 PM
Also, it completely disregards the DisplayMode setting. As you can see from the screenshots the resolution is 1024x768 rather than 800x600 or 640x480. I imagine somewhere in there I'm failing to catch something that tells me that I can't change the DisplayMode.
0

grimpirate Icon

18 December 2011 - 08:56 PM
Hmm... Edit isn't working so I'll post once more and hope it does set off a spam alert. I also think it would be great to provide close buttons on the tabs and instead of placing them at the bottom putting them to the right and with a vertical orientation. I found examples of how to do this but the code was beyond me. It involved messing with the UI of the JComponent and tinkering with its paintComponent method or something to that effect, fairly complex stuff to me.
0

cfoley Icon

19 December 2011 - 06:49 AM
Pretty cool idea. Do you have Java apps for all the tasks you normally do? How about a terminal?

Sounds like whoever wrote that tutorial was overcomplicating things. All you need to is make a panel with a label and a button, and add an action listener to the button to close the tab. Here is a quick example I threw together:

class ClosableTab extends JPanel implements ActionListener {
	private JTabbedPane owner;

	ClosableTab(String title, JTabbedPane owner) {
		super(new BorderLayout());
		this.owner = owner;
		JButton closeButton = new JButton("X");
		closeButton.addActionListener(this);
		
		add(new JLabel(title), BorderLayout.CENTER);
		add(closeButton, BorderLayout.EAST);
	}

	@Override
	public void actionPerformed(ActionEvent e) {
		int index = owner.indexOfTabComponent(this);
		owner.remove(index);
	}
}


I'm sure you can make it pretty yourself. :) Here is an example app which shows how to set a ClosableTab as the tab for the JTabbedPane:

public class CloseTabDemo {

	private JTabbedPane tabs = new JTabbedPane();
	private char nextLetter = 'A';

	CloseTabDemo() {
		JButton newTab = new JButton("[+] New Tab");

		newTab.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				addTab("Tab " + nextLetter++);
			}
		});

		JFrame window = new JFrame("Tab close button demo");
		window.add(newTab, BorderLayout.NORTH);
		window.add(tabs, BorderLayout.CENTER);
		window.setSize(800, 600);
		window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		window.setVisible(true);
	}

	public static void main(String[] args) {
		new CloseTabDemo();
	}

	void addTab(String title) {
		tabs.addTab(title, new JTextArea());
		int index = tabs.getTabCount() - 1;
		tabs.setTabComponentAt(index, new ClosableTab(title, tabs));
	}

}
0

Vampiricx3 Icon

19 December 2011 - 05:02 PM
I would so use this. :D looks cool, kind of like an ubuntu cross with Windows OS.
0

grimpirate Icon

19 December 2011 - 08:15 PM
@cfoley: Very nice code. The Oracle Java Tutorials actually offers a sample to draw the close button graphically with a rollover and whatnot. The trick is tilting it so the orientation is vertical. I think the code might be more complex than it needed to be because it's intended for programming games and making animations with sprites so it's probably intended to do more in later chapters of the book.

The only Java app I use with any degree of frequency is JDownloader, which is a GREAT piece of Java software if you ask me, albeit a tad on the slower side. I want to use more but I've searched and haven't found a great many (I'm sure I'll be disproven though) that are available freely. As for myself I've only ever used Java for console stuff. I find GUI somewhat daunting. This is just something I pieced together. The only console Java app I use here is a simple reminder which pops a JOptionDialog when the timer runs out to remind you of something (like when you're cooking and leave a pizza in the oven).

If I could make a terminal for this I think that would be awesome but you're giving me too much credit cfoley (which I appreciate greatly). I'm sure this code is nonsense compared to how you threw that JPanel stuff together.

@Vampiricx3: lol thanks for the kind words, but you have Java to thank for that theme. I think that would be one of the perks though, if you go to http://www.javootoo.com you can see various look and feels that authors have put together for Java GUI applications. One of my favorites is substance.

@General readers: I'm not a Java developer or even a programmer for that matter, it's more of a hobby for me, but given that the market is producing all these dedicated devices like the iPads, smartphones, Chromebooks, etc. I think it would be a smart move for Oracle to drive development towards machines that would use Java for most everything. Take advantage of Java's cross-platform ability so that you can have an underlying architecture to take care of the nitty gritty, place a greater emphasis on nicer-looking GUIs (I mean most people loathe Java GUIs because of the horrible look of the default theme), and then use core Java for everything else.

The pain of Java really is that it's running alongside other applications and competing for RAM (since it's an interpreted language it needs more RAM and more processing power). My little Dell Inspiron 1100 used to only have 256 MB RAM. I spent a couple of bucks and upgraded it to 1 GB. The fact is system memory has gotten very cheap, and it's no surprise given that most laptops running OSX or Windows 7 now have 4 GB or more on board. Minus the startup time of the JVM, once the stuff is up and running it goes fairly speedy, and if I don't have the overhead of a window manager,(as JDesktopPane inherently acts as one) that just speeds things up another notch and lowers my resource consumption.

Now imagine ONLY Java backed by multiple processors, large amounts of RAM, and a readily accessible video card for acceleration. Assuming the JVM leverages these (I would hope and imagine it does), you could probably have yourself a fairly nifty device on your hands.
0

cfoley Icon

20 December 2011 - 06:48 AM
I've not seen the tutorial you are referring to, but it's certainly possible to have a roll-over button without digging too far into the API. I've modified my previous example to demonstrate. Please excuse the ugliness in the messy code and tabs resizing. I only had 10 mins to hack something together. You should be able to see what's going on though.

public class CloseTabDemo implements MouseMotionListener {

	private JTabbedPane tabs = new JTabbedPane();
	private char nextLetter = 'A';
	private int hoverIndex = -1;

	CloseTabDemo() {
		JButton newTab = new JButton("[+] New Tab");

		newTab.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				addTab("Tab " + nextLetter++);
			}
		});
		
		tabs.addMouseMotionListener(this);

		JFrame window = new JFrame("Tab close button demo");
		window.add(newTab, BorderLayout.NORTH);
		window.add(tabs, BorderLayout.CENTER);
		window.setSize(800, 600);
		window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		window.setVisible(true);
	}

	public static void main(String[] args) {
		new CloseTabDemo();
	}

	void addTab(String title) {
		tabs.addTab(title, new JTextArea());
		int index = tabs.getTabCount() - 1;
		tabs.setTabComponentAt(index, new ClosableTab(title, tabs));
	}

	@Override public void mouseDragged(MouseEvent e) {mouseMoved(e);}

	@Override
	public void mouseMoved(MouseEvent e) {
		int newIndex = tabs.indexAtLocation(e.getX(), e.getY());
		if (newIndex != hoverIndex) {
			if (hoverIndex != -1) {
				ClosableTab tab = (ClosableTab)tabs.getTabComponentAt(hoverIndex);
				tab.hideButton();
			} 
			
			if (newIndex != -1) {
				ClosableTab tab = (ClosableTab)tabs.getTabComponentAt(newIndex);
				tab.showButton();
			} 
			
			hoverIndex = newIndex;
		}
	}

}

class ClosableTab extends JPanel implements ActionListener {
	private JTabbedPane owner;
	private JButton closeButton = new JButton("X");
	
	ClosableTab(String title, JTabbedPane owner) {
		super(new BorderLayout());
		this.owner = owner;
		
		closeButton.addActionListener(this);
		hideButton();
		
		add(new JLabel(title), BorderLayout.CENTER);
		add(closeButton, BorderLayout.EAST);
	}

	@Override
	public void actionPerformed(ActionEvent e) {
		int index = owner.indexOfTabComponent(this);
		owner.remove(index);
	}
	
	void showButton(){closeButton.setVisible(true);};
	void hideButton(){closeButton.setVisible(false);};
}

0

grimpirate Icon

22 December 2011 - 12:27 PM
Look at that, it's not so original a concept:
JD4X
EWM
0

RCR Icon

06 July 2012 - 11:38 AM
Do you mind if I continue on this? As like a joint project. I would give you credit. Just curious.

Thanks

- RCR
0

grimpirate Icon

26 August 2012 - 02:41 PM
Sorry for the delayed response RCR (I haven't checked DIC a lot of late), by all means feel free to use the code at your leisure, no credit needed. I would definitely love to see someone do something like what I thought of to completion.
0
Page 1 of 1