2010-09-26

Install NetBeans on Ubuntu 10.04

Why should one do a description of installing NetBeans on Ubuntu - it's in the repositories and just a few clicks?

The reason is: Installing it through the repositories you will get problems later when trying to upgrade NetBeans with the builtin-update tool. Apart from this I prefer to download the full package from http://netbeans.org/downloads/index.html and deactivating the modules I do not need - at the moment. I always make sure that the sun (or now Oracle) JDK is installed (there were problems in the past with OpenJDK - and although they should be solved I still go with the Sun/Oracle one.

Preparations:
  • You can install the Sun/Oracle JDK through the repositories or do the manual install.
  • To make sure, the correct Java is used, install galternatives (then available through Applications-System Tools-Alternatives Configurator) and then go through each item in the list on the left. There are quite many Java related tools that - after installing the Sun/Oracle JDK now offer that one as second alternative. For all the Java related tools select the new alternative from Sun/Oracle JDK.
  • If you manually installed new JDKs - e.g. after an update has been released, you might have to change the symlink accordingly at /usr/lib/jvm/java-6-sun using the ln command, eg:
    cd /usr/lib/jvm
    sudo ln -s java-6-sun-1.6.0.20 java-6-sun

So here are my steps for the NetBeans installation:
  1. I create a folder under opt for NetBeans:
    sudo mkdir /opt/netbeans
    I don't use the profile folder for installation because this reminds me too much of Windows. In the profile folder there should only go data and I backup this folder more often than other ones. It does not make sense backup the programs all the time.
     
  2. Then I set the permissions for the normal user:
    sudo chgrp theuser /opt/netbeans
    sudo chmod ug+rwx /opt/netbeans
    If you want to have more users work with NetBeans then replace theuser with a group that all the users are member of - however this solution is only if you trust those users regarding updating and activating plugins etc. Otherwise install as a user with sudo permissions and do not set the permissions above to prevent other users from updating.
     
  3. Then I install all NetBeans components (running the installer as normal user) in that folder (/opt/netbeans). There will be usually a subfolder for netbeans (including version in the folder name) and also for Tomcat and Glassfish.
     
  4. I also extract the javadoc downloaded from http://www.oracle.com/technetwork/java/javase/downloads/index.html into /opt/netbeans as jdk-6-docs or so. In former times I put it under the jdk folder but as I upgrade the JDK itself more often than the documentation this always needed additional work to move the doc to the new folder or change links when they got broken after deleting older javadoc.
     
  5. To make the javadoc available in NetBeans launch NetBeans and go to Tools-Java Platforms, switch to the javadoc tab and add the javadoc root folder - in my case /opt/netbeans/jdk-6-docs.
     
  6. When I add external libraries (Tools-Libraries) I also save/extract them previously to another folder - not within my profile (for reasons I mentioned earlier) - of course an option would be /opt/libs or so - I have a completely different separate folder for shared data and libraries that I backup from time to time (less often than profile data).
Related posts: The IDE and the libraries, The programming language.

2010-09-10

Shell scripting your desktop windows

In Java my limits are often what is offered in every OS - and what is inside the JVM.

One thing, I often needed in the past under Windows is to get the title (or other information) of the active window. I was using Windows API to get the information needed. Under Linux/Ubuntu things are different - and more complicated due to different desktop environments, I thought. And how to access those APIs with Java?

Well, it turned out, that I am completely wrong and once again was trapped in my old habits from Windows.

Linux offers commandline tools - not only to get window information - no, also to manipulate windows, move them to different desktop etc. As I could start an application on a different machine and showing the window on my machine, I can either see the machine name in window lists. A commandline program can be easily used from within Java and output can be retrieved easily. Awesome!

It again turns out, that Microsoft Windows is totally poor in commandline tools. However, I could implement a similar small exe that gets me the active window title through API on windows and does console output that can be retrieved by a Java application or can be used in batch files.

If you are a Linux user, have a look at the following commands:
  • wmctrl
  • xprop
  • xwininfo
All these tools can be used even from within shell scripts - awesome!

With xprop -root you get exhaustive information about windows.
Using xprop -root | grep _NET_ACTIVE_WINDOW\(WINDOW\) you get the active window handle. You can then use the address in wmctrl -l | grep theaddress. The only quirks is that in wmctrl the address might have an additional 0 after 0x so you should search for the part after 0x only in the output of wmctrl.

2010-09-09

Dynamic method invocation in Java 6

I often read that people are waiting so much for dynamic method invocation promised for JDK 7 (which now is delayed - see Re-thinking JDK7).

What I wonder is: I already use dynamic class loading and dynamic method invocation in Java 6. Of course, it was a little work, but once done the dynamic class loading and object creation is a one-liner and so is the call to a method:

1:  /*  
2:   * DynTool.java  
3:   */  
4:  package at.mwildam.common;  
5:    
6:  import java.beans.Expression;  
7:  import java.io.File;  
8:  import java.lang.reflect.InvocationTargetException;  
9:  import java.lang.reflect.Method;  
10:  import java.net.MalformedURLException;  
11:  import java.net.URL;  
12:  import java.net.URLClassLoader;  
13:  import java.util.Date;  
14:  import java.util.logging.Level;  
15:  import java.util.logging.Logger;  
16:  import javax.tools.JavaCompiler;  
17:    
18:    
19:  /**  
20:   * Dynamic class loading and method invocation  
21:   * By Martin Wildam (http://www.google.com/profiles/mwildam)  
22:   * @author Martin Wildam  
23:   */  
24:  public class DynTool  
25:  {  
26:    /**  
27:     * Tries to instanciate an object of the given class and returns a pointer to it.  
28:     *   
29:     * Expects the class files in the directory given by the  
30:     * dynClassPath parameter. For each dot (.) in the class path there  
31:     * must be given a subfolder. If there exists a .java file instead of  
32:     * the expected .class file then the method tries to compile the .java file.  
33:     * It tries also a compile if both the .java and the .class file exists but  
34:     * the .class file is out of date (older than the .java file.  
35:     * The addClassPath parameter is only used if the attempt to  
36:     * compile is done.  
37:     *   
38:     * You can also specify a jar file in the dynClassPath parameter if  
39:     * you have a fully compiled and prepared package.  
40:     *  
41:     * Returns null if the object could not be created.  
42:     *   
43:     * Sample: getPluginInstance("/Work/Java/TestGUI/dist/TestGUI.jar", "testgui.TestPluginClass", "/Work/Java/TestGUI/dist/lib")  
44:     *   
45:     * Note: Dynamic class loading and calls evaluated during runtime  
46:     * using reflection is time consuming in general and therefore should be  
47:     * avoided for performance citrical operations (although already better  
48:     * since Java 5).  
49:     *   
50:     * We are returning null here in error case although not recommended  
51:     * because I do agree with Joel Spolsky (http://www.joelonsoftware.com/) on  
52:     * Exceptions at http://www.joelonsoftware.com/items/2003/10/13.html.  
53:     *  
54:     * @param  dynClassPath path to classes root dir where to search for the class to instantiate.  
55:     *     Can also be a .jar file.  
56:     * @param  className Full class name for the plugin class to use.  
57:     * @param  addClassPath is an additional option for classpath to search (;-separated)  
58:     *     which is only used when a compile attempt is done.  
59:     * @return Object or null in error case.  
60:     */  
61:    public static Object getPluginInstance(String dynClassPath, String className, String addClassPath)  
62:    {  
63:      if (!dynClassPath.toLowerCase().endsWith(".jar"))  
64:      {  
65:        String classFile = className.replace(".", "/");  
66:        classFile = dynClassPath + "/" + classFile;  
67:        String javaFile = classFile;  
68:        javaFile += ".java";  
69:        classFile += ".class";  
70:        if (addClassPath.length() != 0) addClassPath = ";" + addClassPath;  
71:    
72:        if ((!existsFile(classFile) && existsFile(javaFile))  
73:            || getFileDate(classFile).before(getFileDate(javaFile)))  
74:        {  
75:          JavaCompiler jc = javax.tools.ToolProvider.getSystemJavaCompiler();  
76:          int r = jc.run(null, null, null, "-classpath", dynClassPath + addClassPath, "-d", dynClassPath, javaFile);  
77:          if (r != 0) return null;  
78:        }  
79:      }  
80:      else  
81:        addFileToClassPath(dynClassPath);  
82:    
83:      try  
84:      {  
85:        if (addClassPath != null && addClassPath.length() > 0)  
86:          addFilesToClassPath(addClassPath);  
87:        URL url = new URL(DynTool.getUrlFromPath(dynClassPath));  
88:        URL[] clsList = new URL[1];  
89:        clsList[0] = url;  
90:        URLClassLoader ucl = new URLClassLoader(clsList);  
91:        Class cls = ucl.loadClass(className);  
92:        return cls.newInstance();  
93:      }  
94:      catch (NoClassDefFoundError ex)  
95:      {  
96:        Logger.getLogger(DynTool.class.getName()).log(Level.SEVERE, null, ex);  
97:        return null;  
98:      }  
99:      catch (ClassNotFoundException ex)  
100:      {  
101:        Logger.getLogger(DynTool.class.getName()).log(Level.SEVERE, null, ex);  
102:        return null;  
103:      }  
104:      catch (InstantiationException ex)  
105:      {  
106:        Logger.getLogger(DynTool.class.getName()).log(Level.SEVERE, null, ex);  
107:        return null;  
108:      }  
109:      catch (IllegalAccessException ex)  
110:      {  
111:        Logger.getLogger(DynTool.class.getName()).log(Level.SEVERE, null, ex);  
112:        return null;  
113:      }  
114:      catch (MalformedURLException ex)  
115:      {  
116:        Logger.getLogger(DynTool.class.getName()).log(Level.SEVERE, null, ex);  
117:        return null;  
118:      }  
119:    }  
120:    
121:    
122:    /**  
123:     * Late binding method call on an already instantiated object.  
124:     *   
125:     * Invokes the requested method of a given object instance where the  
126:     * object class is not specified at compile time.  
127:     *   
128:     * To call a method that does not have a parameter then pass null for the  
129:     * params parameters.  
130:     *   
131:     * What the method returns is routed to the caller of this method as Object  
132:     * so you have to cast the return type to something more specific if needed  
133:     * or just use .tostring. If the called method is declared void then this  
134:     * method returns null.  
135:     *  
136:     * Note: Dynamic class loading and calls evaluated during runtime  
137:     * using reflection is time consuming in general and therefore should be  
138:     * avoided for performance critical operations.  
139:     *   
140:     * We are returning null here in error case although not recommended (because  
141:     * of the earlier mentioned reasons).  
142:     *  
143:     * @param  instance A not well known object for that we hope to be able to  
144:     *     call the requested method.  
145:     * @param  methodName Name of the method to be called  
146:     * @param  params parameter objects to pass to the method (best matching declaration variant is searched)  
147:     * @return Returned object or null if method is declared void or an error occurred.  
148:     */  
149:    public static Object call(Object instance, String methodName, Object... params)  
150:    {  
151:      //Statement stmt = new Statement(obj, methodName, null);  
152:      //stmt.execute();  
153:    
154:      Expression expr = new Expression(instance, methodName, params);  
155:      //expr.execute(); //Not necessary, called automatically on getValue();  
156:      Object result = null;  
157:      try  
158:      {  
159:        result = expr.getValue();  
160:      }  
161:      catch (Exception ex)  
162:      {  
163:        Logger.getLogger(DynTool.class.getName()).log(Level.SEVERE, null, ex);  
164:      }  
165:      return result;  
166:    }  
167:    
168:    
169:    /**  
170:     * Adds a resource given as URL to the classpath.  
171:     *   
172:     * Adds the given url to the classpath dynamically.  
173:     *   
174:     * From antony_miguel at  
175:     * http://forums.sun.com/thread.jspa?threadID=300557&start=0&tstart=0:  
176:     * "  
177:     * I've seen a lot of forum posts about how to modify the  
178:     * classpath at runtime and a lot of answers saying it can't be done.  
179:     * I needed to add JDBC driver JARs at runtime so I figured out the  
180:     * following method.  
181:     *   
182:     * The system classloader (ClassLoader.getSystemClassLoader()) is a subclass  
183:     * of URLClassLoader. It can therefore be casted into a URLClassLoader and  
184:     * used as one.  
185:     *   
186:     * URLClassLoader has a protected method addURL(URL url), which you can use  
187:     * to add files, jars, web addresses - any valid URL in fact.  
188:     *   
189:     * Since the method is protected you need to use reflection to invoke it.  
190:     * "  
191:     *   
192:     * The class path change does not reflect in the system property  
193:     * "" because that property does not get modified any more after application  
194:     * start. So don't check success by checking  
195:     * System.getProperty("java.class.path");.  
196:     *  
197:     * @param  url Url to add to the class path.  
198:     * @return True if operation was successful  
199:     */  
200:    public static boolean addUrlToClassPath(URL url)  
201:    {  
202:      if (url == null)  
203:      {  
204:        Logger.getLogger(DynTool.class.getName()).log(Level.WARNING, "Missing url to add to classpath.");  
205:        return false;  
206:      }  
207:    
208:      boolean b = false;  
209:    
210:      URLClassLoader sysloader = (URLClassLoader) ClassLoader.getSystemClassLoader();  
211:      Class sysclass = URLClassLoader.class;  
212:      try  
213:      {  
214:    
215:        Class[] methodParams = new Class[1];  
216:        methodParams[0] = URL.class;  
217:        //There could be really different classes in the array.  
218:        @SuppressWarnings("unchecked")  
219:        Method method = sysclass.getDeclaredMethod("addURL", methodParams);  
220:        method.setAccessible(true);  
221:        method.invoke(sysloader, new Object[]  
222:            {  
223:              url  
224:            });  
225:        b = true;  
226:      }  
227:      catch (IllegalAccessException ex)  
228:      {  
229:        Logger.getLogger(DynTool.class.getName()).log(Level.WARNING, null, ex);  
230:      }  
231:      catch (IllegalArgumentException ex)  
232:      {  
233:        Logger.getLogger(DynTool.class.getName()).log(Level.WARNING, null, ex);  
234:      }  
235:      catch (InvocationTargetException ex)  
236:      {  
237:        Logger.getLogger(DynTool.class.getName()).log(Level.WARNING, null, ex);  
238:      }  
239:      catch (NoSuchMethodException ex)  
240:      {  
241:        Logger.getLogger(DynTool.class.getName()).log(Level.WARNING, null, ex);  
242:      }  
243:      catch (SecurityException ex)  
244:      {  
245:        Logger.getLogger(DynTool.class.getName()).log(Level.WARNING, null, ex);  
246:      }  
247:    
248:      return b;  
249:    }  
250:    
251:    
252:    /**  
253:     * Converts a file path to an url link.  
254:     *   
255:     * Returns the url link equivalent to the given path as string.  
256:     *  
257:     * @param  path  
258:     * @return Url string  
259:     */  
260:    public static String getUrlFromPath(String path)  
261:    {  
262:      try  
263:      {  
264:        return new File(path).toURI().toURL().toString();  
265:      }  
266:      catch (MalformedURLException ex)  
267:      {  
268:        return "";  
269:      }  
270:    }  
271:    
272:    
273:    /**  
274:     * Returns a boolean true if a "normal" file with the specified name exists.  
275:     *   
276:     * Note: Returns false for files that are part of the kernel system.  
277:     * So it returns true only for "normal" files.  
278:     *  
279:     * @param  fileFullName FQPN of the file to be searched for  
280:     * @return Boolean  
281:     */  
282:    public static Boolean existsFile(String fileFullName)  
283:    {  
284:      if (fileFullName == null)  
285:        return false;  
286:      else  
287:      {  
288:        File f = new File(fileFullName);  
289:        return f.exists() && f.isFile();  
290:      }  
291:    }  
292:    
293:    
294:    /**  
295:     * Returns the timestamp of the file with the given name if exists otherwise 0.  
296:     *   
297:     * Returns a Date of 0 if the file could not be found otherwise the last  
298:     * file modification date.  
299:     *  
300:     * @param  fileName Name of the file from which to read the date and timestamp  
301:     * @return Date  
302:     */  
303:    public static Date getFileDate(String fileName)  
304:    {  
305:      if (!existsFile(fileName))  
306:        return new Date(0);  
307:      else  
308:      {  
309:        File f = new File(fileName);  
310:        return new Date(f.lastModified());  
311:      }  
312:    }  
313:    
314:    
315:    /**  
316:     * Adds the given file dynamically to the class path.  
317:     *   
318:     * Further details see {@link #addUrlToClassPath(java.net.URL) }.  
319:     *  
320:     * @param  file File object to be added dynamically to the class path.  
321:     * @return True if operation was successful.  
322:     */  
323:    public static boolean addFileToClassPath(File file)  
324:    {  
325:      if (file == null)  
326:      {  
327:        Logger.getLogger(DynTool.class.getName()).log(Level.WARNING, "Missing file to add to classpath.");  
328:        return false;  
329:      }  
330:    
331:      URL url;  
332:      try  
333:      {  
334:        url = file.toURI().toURL();  
335:      }  
336:      catch (MalformedURLException ex)  
337:      {  
338:        Logger.getLogger(DynTool.class.getName()).log(Level.WARNING, null, ex);  
339:        return false;  
340:      }  
341:    
342:      return addUrlToClassPath(url);  
343:    }  
344:    
345:    
346:    /**  
347:     * Adds the given file dynamically to the class path.  
348:     *   
349:     * Further details see {@link #addUrlToClassPath(java.net.URL) }.  
350:     *  
351:     * @param  fileName FQPN of the file object to be added dynamically to the class path.  
352:     * @return True if operation was successful.  
353:     */  
354:    public static boolean addFileToClassPath(String fileName)  
355:    {  
356:      return addFileToClassPath(new File(fileName));  
357:    }  
358:    
359:    
360:    /**  
361:     * Adds files in the given folder dynamically to the class path.  
362:     *   
363:     * You can specify multiple paths separating them by ";".  
364:     * Further details see {@link #addUrlToClassPath(java.net.URL) }.  
365:     * Path must contain only jars and class files (subfolders not included).  
366:     *  
367:     * @param  path FQPN of the path to be added dynamically to the class path  
368:     *     or multiple paths separated by ";".  
369:     * @return Number of jars and class files added.  
370:     */  
371:    public static int addFilesToClassPath(String path)  
372:    {  
373:      if (path == null || path.length() == 0) return 0;  
374:      int n = 0;  
375:    
376:      if (path.contains(";"))  
377:      {  
378:        String[] subPaths = path.split(";");  
379:        for (String subPath : subPaths)  
380:        {  
381:          n = n + addFilesToClassPath(subPath);  
382:        }  
383:      }  
384:      else  
385:      {  
386:        File dir = new File(path);  
387:        File[] contents = dir.listFiles(); //Path Must contain jars and class files only  
388:        for (int i = 0; i < contents.length; i++)  
389:        {  
390:          File file = contents[i];  
391:          if (addFileToClassPath(file))  
392:            n++;  
393:        }  
394:      }  
395:      return n;  
396:    }  
397:    
398:  }  
399:    
400:    

You might prefer looking at this code on pastebin.

Sample usage:
  1. Object obj = getPluginInstance(yourJarFile, yourPluginClassName, additionalLibPath);
  2. Object result = call(obj, "pluginMethod", param1, ...);
    // For no parameters use at least null for param1
I use this currently in two cases:
  • For loading plugins at runtime (in those cases I only need getPluginInstance because I cast the object to a known interface class).
  • To avoid fixed dependencies of common utility libraries (in those cases I only need the call method). What I mean with this? - To explain, a little example: I have a Swing utility class that runs through all elements of a JFrame or JDialog and returns a map with all widget names and values it can extract. This way, saving dialog inputs is a one-liner. However, I support a lot of different external widgets, most are not used in 80% of my projects, so I don't want to introduce the dependencies to all those external components not included in the Swing core. Using the dynamic call offers the support for those widgets when I find them in dialog contents (identified by class name) where they occur. And the appropriate dependencies are only needed in the particular projects where they are needed.
So I don't really understand, why so many people are arguing against Java, that dynamic calls are not possible - where my sample code either automatically compiles a class that is given as .java instead of packed into a compiled .class or .jar file. And if I have both I check the class file if it is older and compile only in that case. So I would say, this is dynamic Java language!

Related posts: Get reliable local IP address in Java, Popular Java myths.

2010-09-04

Use cases for netbooks

For a long time I could not see a sense in netbooks. I am a developer and hence write a lot of code and a lot of documentation. The screen of a netbook seemed too small at all and keyboards too. But there are a few cases when it makes really sense to carry a netbook instead of a full notebook (that is heavier, and occupies more space on the table), for example:
  • When I sit in front of the TV with my family, for checking a little email or do a little web surfing, it is perfectly enough and I can put it aside easier simply because needs less space and if it has enough battery power, you either don't need the cable having around all the time even if it is kept in reach for a few hours.
     
  • I have a customer where employees walk around with mobile phones for entering data on the street. The data typing could be done more efficiently on a netbook than on a mobile phone if they sit in the car or have somehow something to put it down (wall, table, knee, ...).
     
  • Many IT admins need to be available quite all the time in case of emergencies. I have heard of some doing SSH over the mobile phone in such emergency cases when they are on the way - and they don't want to always carry a notebook with them. If you are a server admin only a mobile phone might be sufficient but I am doing also client support and it sounds just crazy to me doing end-user-support through the mobile phone. I am used to carry a small bag with me (to avoid having the wallet, keys and the like in different small pockets where they either could be stolen easier) which I also do recommend to others and with only a little bigger bag the netbook already fits in there.
     
  • There are a lot of branches where salesmen found it exaggerated to carry a notebook with them. However, looking up customer or product data through the phone is frickle work IMHO. A netbook fits there well I would say.
     
  • When I go to meetings outside the office (e.g. in hotels or restaurants) a netbook is more handy than a big notebook and in those cases I usually just need to take a few notes, do a little surfing or email.
     
  • Despite all the features that are put into smartphones these days, I hardly think, they will get a full features PC soon. PGP plugins for email, Password managers like keepassx, serious word processors and many other things I use on my normal notebook are missing. On a netbook I can use all these as usual - it's just the screen that is smaller (but still bigger than on every smart phone).
     
  • After ripping DVDs to disk a netbook can also be used as a mobile DVD player.
Related posts: The Dell Latitude 2110 and Ubuntu 10.04.1, Ubuntu compatible hardware, The mobile device, The hardware.

The Dell Latitude 2110 and Ubuntu 10.04.1

Despite the rumors that Dell does not offer Ubuntu any more, I was able to get the Dell Latitude 2110 with Ubuntu (netbook remix) preinstalled - so (again) no Microsoft tax paid. ;-)

I must admit, that they first did not want to give me the full featured thing (the optional Ubuntu version comes without webcam), but I insisted and got the thing with webcam and Ubuntu preinstalled. In reality they could have shipped without OS, the first thing anyway was putting the latest Ubuntu 10.04.1 LTS Lucid Lynx on it (because it came with 9.10 which worked out-of-the-box on delivery).

Installation of 10.04.1 needed a few quirks: After I installed from an USB stick, I had to add the following additional repositories to get the WLAN to work:
  • deb file:///media/yourusbsticksname lucid main
  • deb file:///media/yourusbsticksname lucid restricted
After that I could go to System->Administration->Hardware Drivers and searching it found the driver for the WLAN. I was fearing more trouble with the WLAN as this is quite usual (not only for Linux) in the netbook world to have troubles with the WLAN (as far as I have read in forums). But just adding the USB-stick as repository is not much trouble (although it took me a while to find out ;-) ).

Webcam worked out-of-the-box (I was assuming to get problems with that because missing in the Ubuntu variant by default, but as working, I think there are just political reasons that they omit the web cam for the Ubuntu variant by default).

Anyway, what did not work out-of-the-box was the built-in microphone: Again, I had to add another repository and install a single patch from there (see Bug #563215 and below):
  • ppa:ubuntu-audio-dev

Then install this package:
sudo apt-get install linux-alsa-driver-modules-$(uname -r)
In addition to that I had to go into terminal window and run "alsamixer" - it shows more volume nobs that you might have known or guessed. I had to push up two from the input section. I guess Skype did that after installation as by default it sets the option allowing the program to manipulate audio settings (I already had this several times on other Ubuntu installations that Skype modified the mixer settings messing them up).

Finally I had to plug in a headset just for a few seconds - then also the external microphone worked. Strangely it did not work until I plugged in the headset's mic in once. Since then it works. But I have to say that speakers and microphone are not top-level. The speakers could be louder and mic could be better also - so in noise environments (I have two little children around ;-) ) you should use a headset.

My two mobile internet sticks work too - just not with autoconnect (it seems both try to connect - same happens on my main Lucid notebook also so guess it is a general issue introduced a short while ago - having configured just one works fine).

The netbook feels very well made. The keyboard is big enough to be used without hassle and it feels robust (as usually for Dell Latitude keyboards). I have ordered the bigger battery which adds a little to weight and size of course. The bigger battery extends versus bottom so it makes the keyboard getting a slightly more diagonal position so now the netbook does not lie flat on the table. This way it is more ergonomic IMHO. Time estimated with fully charged battery pack is more than 6 hours. The only thing: It was harder to find a matching case with the extended battery. The normal small bags for 10,1" notebooks were fitting only very tight which was a hassle. Finally I bought a larger bag where now also fit more other things.

Related posts: Ubuntu compatible hardware, About Dell, The hardware, The mobile device, Going Linux, The sad thing about Linux..., Use cases for netbooks.

    Ubuntu compatible hardware

    In the last months I faced several different hardware environments and experienced several issues when installing Ubuntu. For example: Since 2005 I always recommended NVidia and told people to better stay away from ATI. But lately I had problems with a machine with NVidia and on the other hand heard from a friend about an Ubuntu machine with ATI running flawlessly. So I started to fear for issues no matter what hardware I recommend.

    I talked with several people and sent out emails and the result is a collection of links that help a lot. First of all - what I did not know - there is an Ubuntu certification for hardware - and there is more:

    And more hardware help is available here:
    Related posts: The hardware, Why Linux?, Going Linux, The sad thing about Linux, About Dell, Ubuntu 10.04 experiences, The Dell Latitude 2110 and Ubuntu 10.04.1, Use cases for netbooks, The truth about hardware support.

    2010-09-02

    User-friendly Mac OS

    Many people tell that Mac OS is the mos user friendly operating system. Here extraction from a user manual of an external USB hard drive for Mac Users:

    How to remove the drive - for Mac OS Users:
    1. If you have files located on your drive open in any applications, close them.
    2. Locate the icon for your drive on the desktop, and drag it to the trash.
    I am not a Mac user, but I never ever would have done that for removing the drive! Dragging something to the trash for me means "Delete it - delete the contents"!

    What if I accidently would have dragged selected files from the drive to the trash and not the drive itself. Do you call this user-friendly?

    Related post: Why Linux?