Thursday, August 7, 2014

Uninstalling apps that just.wont.uninstall.

And I'm tinkering again. This time, with my recently-rooted and flashed Android phone.

As I mentioned I'd been experiencing random reboots; one suspect was an app called Mirror, developed by CM Guru Koushik Dutta. I came across his thread in Google+ and, curious (as always), I installed the app. 
Unfortunately, it force-closed almost immediately; and worse! It wouldn't uninstall using any of the following:

  • adb uninstall com.koushikdutta.mirror
  • Settings > Apps did not even have an Uninstall option.
  • adb shell >>> pm uninstall com.koushikdutta.mirror
The fact that there was no Uninstall option made me think that perhaps the app installed itself as a system app.

So - thought I'd write this guide for myself as well as for any hapless newly-born tinkerers out there.

Launch adb with root access

  1. Root access for adb is usually disabled. Visit CyanogenMod's Developer Options and set Root access for adb (Settings > Developer Options > Root Access > "Apps and ADB") 
  2. Run adb root.
  3. Now launch a root-enabled shell.

Mount your /system partition and delete the app's data

mount -o rw,remount -t yaffs2 /dev/block/mtdblock3 /system
rm /system/app/your_app
rm /data/data/your_app

Edit /data/system/packages.xml and /data/system/packages.list

Remove any references to your suspect package in /data/system.
  1. First pull it to your machine as adb pull /data/system/packages.xml
  2. Edit it in your favorite editor (mine's Emacs on Windows)
  3. Push it back as adb push packages.xml /data/system
  4. Repeat the same steps for packages.list

Monday, August 4, 2014

Installing CM 11 on the Galaxy Grand Duos

It happens to the best of us. And to me, it happened one rushed evening. I crowded into an already-super-crowded Mumbai Local. When I got off at the next station, barely 5 minutes later, my phone was missing, presumed stolen.

(And that should also explain my relative absence).

Surprisingly enough, I wasn't too sad. Gadget cravings have mostly abated thanks to a never ending stream of devices and Android versions. As a result of which, beyond the initial starry-eyed delight, every Android device is pretty much exactly the same. It's the iPhone-ification of Android - to make every phone boring.


Anyway... I took this as an opportunity to install CyanogenMod 11 on a Galaxy Grand that my wife generously gave me. 


Word of warning: CM11 on the Grand keeps randomly rebooting. I'm unsure if this is due to the ROM or the phone or both. Haven't had the time to investigate as yet.


1. Install ClockworkMod Recovery


If you've done this before, it's a piece of cake. If not, ClockworkMod Recovery is the first thing you need to do.
The link above is already well-written so I won't go into the details.
Words of warning though: 

  1. This will void your warranty and/or brick your device if you are not careful.
  2. Some links above may be dead.

2. Install CM 11

Source.

I followed the advice in the above link. 

3. Install Google Apps

The last thing you need to do is install Google Apps. Due to Copyright issues these are not bundled with the install. You can get them here.

And that's it - done!

Review


Pros
  • CyanogenMod is great! And with the Galaxy Grand, even better as you don't have to contend with Samsung's crappy TouchWiz UI any more.
  • There are some little features that make a huge difference. For instance; in the notification shade, tapping toggles a quick setting while long-pressing takes you to more options for that setting. Just the way it should be. 
Cons
  • Perhaps it's due to the fact that the Galaxy Grand release is unofficial. But I'm facing random phone reboots - mostly when sharing an image or capturing a photo. I haven't had the time yet to look at the logcat logs.
  • Why so many Hexagons??

Monday, July 21, 2014

Swapping the HDD for an SSD on the Lenovo U310 Ultrabook

There was a time, before SSDs and Ultrabooks, when life was simple. You received your "laptop" from a generous uncle or cousin; or perhaps you scrimped and saved until you got one. It would be smooth going: the 9+ pound weight wasn't a concern; 5400 rpm of hard-drive speed was great, for they'd couple that with a whopping 80 Giga (!) Bytes of space. 

Tinkering was restricted, mostly, to those occasions when you'd spill your late-night coffee all over your keyboard and your laptop would start randomly popping open windows and typing strange chat messages to strangers and your keys would become all ... sticky. "I popped open all the keys and cleaned my laptop today," you'd say proudly to anyone willing to lend a year. Perhaps at the end of your laptop's life, you'd replace the battery with a cheap Chinese knockoff, or reinstall Windows. 

Heck, once upon a time, my laptop started blue-screening every time it hit a bad sector. It took 3 or 4 trial-and-error attempts, but I finally partitioned off the bad sector into a 2GB "quarantine" and installed Windows on the rest of the 80GB drive. I even managed to sell it off on EBay!

But ... those were the good old days (*sigh*). Enter SSDs. Suddenly, our laptops were just. not. fast. enough! To go back to an HDD after trying out your friend's shiny new MacBook was like going back to public transport after having driven a car. And Ultrabooks totally messed it up for the budget-conscious laptop buyer. From being okay with spending a few hundred dollars, now we were forced to fork out at least double that, for a yesteryear almost-Ultrabook. And once you'd been with an Ultrabook, you couldn't go back to a regular full-sized laptop. Again, *sigh*.

And so it was, that when my existing Ultrabook died (I dropped it during a playful tussle with my nephew), I was forced to look for an alternative. 
The cheapest alternative out there, was a 42000 INR (I had returned to India by then) Lenovo U310. Even the date of manufacture on the label made me balk - it was a year old at purchase, and was probably sitting on a store shelf gathering dust! Either way, I had to get it. It was a relative steal at the price. 

Barely 7 months after purchase, the laptop started feeling slow. This was not helped by the fact that my work laptop was a Core i7 + 128 GB SSD beast. So, I decided to make the switch from HDD to SSD! 

Swapping the HDD with SSD - what you need

  • Swapping out the Lenovo laptop's Hard Disk is child's play if you have done it before. I have to say that Lenovo has made it very easy to access the Hard Drive Bay. 
  • If you haven't swapped out HDDs before, I strongly recommend proceeding with caution. The back cover and parts are cheap plastic and will break or at-least chip off if you aren't careful.
Ingredients
  • An SSD, or solid state drive, around INR 6000-10000 at time of writing. Try searching Flipkart for a good deal. 
  • An installation ISO for Windows
  • A USB thumb drive, greater than 4 GB.
  • An expired credit/debit/shopping rewards card.
  • A good screw driver set.
  • An ESATA-to-USB adapter case.
  • An External Hard Drive
  • Windows 7 USB/DVD/Download Tool

Prepare

  1. Back up your existing data to the external Hard Drive.
  2. Using Windows 7 USB/DVD Download Tool, create a bootable USB Drive using the 4GB Thumb Drive and the Windows 7 ISO.
  3. You will use this thumb drive later, to install Windows.

Remove the rubbery pads

Remove the rubbery pads using your (uncut) fingernails
Point of no return
  1. First remove the four rubbery pads from the bottom of your Lenovo.
  2. Use your extra-long fingernails to do this.
  3. It might take some effort but eventually you will get through.








Unscrew the back cover

  1. Using a screwdriver, remove the 4 screws that are now exposed.
  2. See the little sticker in the image? That voids your warranty (moment of truth!)



Remove the back cover



There goes my Zipcard!

Start at the hinge, proceed clockwise

  1. Now use the plastic card and gently slide it under your case, just near the screen hinge.
  2. Jiggle it up and down until you hear a snapping sound. This means the fastening buckle has come out. Do it gently or else the buckle will break!
  3. Proceed clock-wise until each buckle has snapped open.
  4. The last buckle is especially tricky and can snap if you are rough!
  5. After all the buckles are out, gently prise the case open. It should come out easily.

Remove the Hard Drive

Unfasten the screws
Connect the SATA connector properly

  1. This is the easy part if you know your way around a screwdriver and HDD.
  2. The hard drive is fastened to a case, and that case is fastened to the board by 4 screws. Take out these 4 screws.
  3. Gently lift the hard drive + case. Unfasten the SATA connector. 
  4. Detach the Hard Drive from its case by removing the 4 screws on the sides.

Insert the SSD

  1. Attach the SSD to the case in exactly the same alignment as the HDD was. The holes in the case and the grooves in the SSD should align without trouble.
  2. Reattach the 4 case screws. 
  3. Reattach the SATA connector. Don't force it in! Take note of how it aligns. 
  4. Place this connected SSD into the hard drive slot so that the screw grooves in the laptop align with the corresponding holes in the HDD case. 
  5. Screw it down tightly.

Replace the Case and Rubber Pads

  1. Replace the case and rubbery pads. This is not as easy as it looks!
  2. Each of the 4 rubber pads has a number. You need to match it with the corresponding number on the laptop's case!

Start it up!

  1. Hopefully you have done everything right. Start the machine, with the USB bootable disk connected.
  2. You will need to hold down Fn + F2 during startup to boot into Setup.
  3. Under the boot menu, check that you see the USB Thumb Drive as well as your SSD.
  4. Move the Thumb Drive to the top of the list. Save and restart.
  5. Now, the laptop will start using the thumb drive. Install Windows like you normally would, and you are good to go!









Sunday, June 29, 2014

Help! My landlord sold my apartment while I was still leasing it!

To continue along the vein of "Something about Everything", here is a post about the Maharashtra Rent Control Act, 1999!

Have you faced the following situation?

  • You are leasing an apartment (with a legal Leave and License Agreement);
  • Your landlord decides to sell the apartment;
  • You are unsure how this affects you?
I'm facing somewhat of a similar situation, and the question foremost on my mind is, can the new landlord evict me if he chooses?

I did some Googling and didn't find anything specific to India (of course, I did find a lot of Yahoo Answers without any reference). So I did a little "light reading" (ha!).

Here's the link (PDF) to the rather long Maharashtra Rent Control Act, 1999 which governs pretty-much everything concerning leases. (If the link is dead, try googling for the phrase "Maharashtra Rent Control Act".)

Disclaimer
  • I am not a lawyer; just a stressed-out tenant trying to make sense of it all;
  • I have tried to interpret the rent-control laws as best as I can. If you think I am wrong, please leave a comment!

The relevant portion is in Chapter V, Sub-Section (2), Clause (3). If you can't find it, search for the word "purchase". There are luckily only two matches, both in the same paragraph.

To cut to the chase, here's what the relevant paragraph says (copied verbatim):

Explanation.- A landlord or his successor-in-interest by inheritance or otherwise shall not
be entitled to recover possession under this section from the tenant or his successor-in interest by transmission, where the landlord has acquired the property by purchase, gift,
exchange or otherwise (but excluding acquisition by inheritance or succession or in the
case of premises in a Co-operative Housing Society, by acquisition of a share or right and
interest in such premises by nomination), and where, at the time of acquisition, by
purchase gift, exchange or otherwise the premises had been in the occupation of the
tenant or his predecessor-in-interest from whom the tenancy has been transmitted and
notwithstanding anything contained in any judgement, decree or order of the court or
anything contained in this Act or in any other law for the time being in force, the
provisions of this explanation shall always be deemed to have applied to such a case, and
the landlord shall not be entitled to recover possession in any such case,

What does this mean?
  • "A landlord or his successor-in-interest" - refers to the landlord; let's leave out the "successor-in-interest" part, for simplicity
  • "shall not be entitled to recover possession under this section" - means that he/she cannot evict you, the tenant
  • "from the tenant or his successor-in interest by transmission" - means you the tenant; again, let's leave out the "successor-in-interest" part for simplicity
  • "where the landlord has acquired the property by purchase, gift, exchange or otherwise" - i.e where the landlord has purchased this property from someone
So, to summarize:
"The landlord cannot evict you if he/she has purchased the property from someone else."


Wednesday, June 18, 2014

Fully Automated Google Calendar events from Emails

I love Google Now. It has helped me out of a pickle more than once, especially with its automatic reminders about flights. And now it's expanded into hotel reservations and bus timings as well! *Sigh* At least in the US.

With my move to India, however, gone are the days when I could rely on Google Now. It *sometimes* catches flight ticket emails and registers an event reminder; mostly it fails.

Plus, after all, this is India. That is to say, I travel by trains weekly; so it's trains that I'm interested in. And yes, FYI, I have forgotten to travel on trains before. Slave to technology, fat-slobbering-man-from-WALL-E, call me whatever.

So, assuming you have Google Now properly configured to read your email and calendar, how do you get automatic reminders for upcoming trains?

Let's back up for a minute and examine the facts:
  • Trains in India are primarily reserved through IRCTC.
  • The website sends out a very very templatish email, and that template has not changed in years.
  • Suppose there were a service that could run on every email, and extract the details?
  • Suppose that said service could magically also create a Calendar entry? 
  • In that case, Google Now would then (hopefully) do its job, and display a neat reminder - "Hey, you have a train!"
  • Even in the worst case, Google Calendar would display a notification.
Enter Google Scripts. Using this nifty little tool, I was able to create a script that extracts the relevant info and creates a Google Calendar event!
Obviously, this was a half-day job so it's rusty. Here's what it does:
  • Looks at the latest 20 emails for subject-line "Email From IRCTC"
  • Extracts the information about your upcoming reservation
  • Looks at the journey date in your default calendar
  • If no events are found with text "Train to ", it creates a calendar entry.
  • It sets a popup reminder for 12 hours before your journey.
I loved Google products before, and now I swear by them!


/**
* Looks for emails from IRCTC that announce a train reservation.
* Then extracts email info
* Then adds a calendar entry to your default calendar
* author: vish
*/
function createIRCTCCalendarEntry() {
  //Match Train Name
  var trainNo = new RegExp("<b>Train No</b></td><td.*>([0-9]+)</td>","m");
  var trainName = new RegExp("<td.*><b>Train Name </b></td>$\n^<td.*>([a-zA-Z ]+)</td>","m");
  var trainFrom = new RegExp("<b>From</b></td>$\n^<td.*>([a-zA-Z ]+)</td>","m");
  var trainTo = new RegExp("<b>Reservation Upto</b></td>$\n^<td.*>([A-Z]+)<td","m");
  var trainDate = new RegExp("<b>Date</b></td>$\n^<td.*>([0-9\-]+)","m");
  
  
  Logger.log("Hello World");
  var threads = GmailApp.getInboxThreads(0, 20);
  for (var i = 0; i < threads.length; i++) {
    var messageSubject = threads[i].getFirstMessageSubject();
    if (messageSubject == "Email From IRCTC") {
      Logger.log(threads[i].getId() + " is an IRCTC email");
      //get message body
      var messages = threads[i].getMessages();
      var msgBody = messages[0].getBody();    
      Logger.log(msgBody);
      
      //extract relevant details about train
      var match = trainNo.exec(msgBody);        
      var no = match[1];
      var match = trainName.exec(msgBody);        
      var name = match[1];
      var match = trainFrom.exec(msgBody);        
      var from = match[1];
      var match = trainTo.exec(msgBody);        
      var to = match[1];
      var match = trainDate.exec(msgBody);        
      var date = match[1];
      /*
      Set description for calendar event
      */
      var desc = "Train no " + no + " (" + name + ")" + " from " + from + " to " + to + " on " + date;
      Logger.log(desc);
      
      /*
      Proceed only if no null values
      */     
      
      if (no == null || name == null || from == null || to == null || date == null) {
        Logger.log("train details were not parsed correctly; not creating calendar entry");
      } else {
        /*
        Extract Date and convert to appropriate form
        */
        var year = date.split("-")[2];
        var month = date.split("-")[1] - 1;
        var day = date.split("-")[0];
        var desiredDate = new Date(year, month, day);
        Logger.log(year + month + day);
        
        /*
        Create event title
        */
        
        var desiredTitle = 'Train to ' + to;
        
        /*
        If Calendar already has events with exact same name
        Do not proceed
        */
        Logger.log("Creating event in default calendar " + CalendarApp.getDefaultCalendar().getName());
        var eventsForDay = CalendarApp.getDefaultCalendar().getEventsForDay(desiredDate);
        for (var x=0; x<eventsForDay.length; x++) {
          Logger.log("Existing event was found for " + date + " with title: " + eventsForDay[x].getTitle());
          if (eventsForDay[x].getTitle() == desiredTitle) {
            Logger.log("event already exists, aborting!");
            return;
          } else {
            var event = CalendarApp.getDefaultCalendar().createAllDayEvent(
              'Train to ' + to, 
              desiredDate,
              {description: desc});
            Logger.log("Event " + event.getId() + " created on " + event.getDateCreated() + " in calendar " + CalendarApp.getDefaultCalendar().getName()
            + " with start date " + event.getStartTime().toString() );
            event.addPopupReminder(720);
          }
        }
      }
    }
  }
}

Sunday, June 8, 2014

Once upon a morning-glory-timelapse

What's the use of three balconies if you can't have a garden? Both of us had been looking to grow plants for a while. So, when I got a chance to visit Bangalore, I jumped at the chance.

Bangalore is not known as the city of gardens (or is it flowers?) for nothing. And Lalbaugh, of course, is its epicenter. Off the tourist trail is a very sophisticated plant nursery with the widest array of flowers you'll see.






(When you enter from the KH Road (Double road) entrance to Lalbagh, keep walking straight until the hill - then turn left. Here's a Google+ entry)

Not just flowers - there's even vegetable seeds! After browsing forever, and wishing I knew the local language (the gardeners speak nothing but Kannada, sadly) I picked up petunia, parsley, fenugreek (methi), kanakambaram and morning glory. 

And then I browsed around a little, before (reluctantly) returning to work. (Yes, I'd slipped out during a particularly boring unproductive day at work). 

That had been in February. Over the next three months, I and my wife tended, nurtured and pruned away at the saplings. We saw the petunia grow, die off, grow again and finally die off with heavy hearts. I guess not everyone of those seedlings was suited to the Pune climate.

The morning glory however was a tough little one. And since the day I pressed that first seed into the mud, I'd wanted to capture its growth. 

So, on a particularly hot sunny day today, I set up my camera and tripod to start capturing the various movements of this particularly active plant in a timelapse. I then went off to work, oblivious of the events to come. 

At around 3, I heard a rhythmic rapping on my cubicle's window. I looked out to see the darkest rainclouds and the gustiest winds I'd seen so far. And I'd left my camera out facing the window. That's the damnnnn moment you never want to get, I guess. So I raced home to find all my balcony and parts of my bedroom completely drenched. 
And sitting in the middle of it all, like this magnesium-alloy saint meditating, was my camera - still clicking away.

"This better be worth it," I kept muttering, cleaning the grime off the lens and ejecting the SD card. And .... it was so worth it!

Here's the result. Enjoy!



Friday, May 23, 2014

Running Selenium WebDriver Tests over Bamboo

In keeping with the tradition of writing "something about everything", here's today's "everything": Selenium WebDriver over Bamboo!

This topic deals with a commonly-encountered situation - running headless Selenium WebDriver tests over Bamboo. WebDriver's great; but when coupled with the headless situation of Bamboo, things can go a little (a lot) haywire. 

Situation

This blog post is useful if:

  • You have a Selenium WebDriver test suite that runs well on your local machine.
  • You have a working install of Bamboo, and wish to run your tests over Bamboo

What you need:

  • A working knowledge of Selenium WebDriver, Java, Bamboo
  • The patience to deal with a problem :)

Why can't I just run Bamboo + Selenium?

Try running it - you'll see for yourself.

On my Selenium WebDriver tests I encountered the following error when the ChromeDriver is starting up:

23-May-2014 08:37:59 Starting ChromeDriver (v2.9.248304) on port 12815
23-May-2014 08:37:59 [0.174][WARNING]: PAC support disabled because there is no system implementation
23-May-2014 08:39:00 org.openqa.selenium.WebDriverException: unknown error: Chrome failed to start: exited abnormally
23-May-2014 08:39:00   (Driver info: chromedriver=2.9.248304,platform=Linux 3.2.0-23-generic x86_64) (WARNING: The server did not provide any stacktrace information)
23-May-2014 08:39:00 Command duration or timeout: 60.29 seconds
23-May-2014 08:39:00 Build info: version: '2.40.0', revision: '4c5c0568b004f67810ee41c459549aa4b09c651e', time: '2014-02-19 11:13:01'
23-May-2014 08:39:00 System info: host: 'icelero-server-dl360e', ip: '127.0.1.1', os.name: 'Linux', os.arch: 'amd64', os.version: '3.2.0-23-generic', java.version: '1.7.0_51'

Set up Bamboo Plan


I'm assuming you have the knowledge to set up a Bamboo plan that runs your WebDriver script.

The only thing you'd do differently is, to pass a parameter called DISPLAY to the Bamboo Plan.

  • Go to the Plan Configuration page for your Bamboo Plan (Actions > Configure Plan)
  • Hit the "Variables" tab.
  • Pass a DISPLAY parameter, with the value :10
  • (We will discuss the significance of this later).



Install, configure and start Xvfb


Obviously, the reason you cannot run Bamboo-based Selenium jobs, has something to do with the fact that we're running headless.  


Here's the first clue.
Specifically the part that says, 
In your jenkins settings add a global property key : DISPLAY value:0:0 ... On your server start Xvfb in the background:Xvfb :0 -ac -screen 0 1024x768x24 &;

So we need Xvfb, which will basically fake a display to Chrome. 
Installing Xvfb is pretty simple. On my Linux Ubuntu server, it's 
sudo apt-get install Xvfb

Now start Xvfb as follows:

Xvfb :10 -screen 0 1024x768x24 &

Make sure you use the same display number everywhere. In this blog post I use :10.


Set an environment variable


In order for your Bamboo job to see the display, you need to set a DISPLAY variable on your machine.
This can be done in two ways:

a. Set it in /etc/environment

Edit the /etc/environment file, and add a value:
DISPLAY=:10
You need root privileges to do this.

b. Set it locally for the test-session

Remember how you passed a Bamboo variable to your test?
You can access that variable using the ${bamboo.varname} construct.
In our case, we can access it as ${bamboo.DISPLAY}.

To set a DISPLAY variable that's local to the session, we can do:
export DISPLAY=${bamboo.DISPLAY}

However, this method is untested.

Restart your machine

This is especially true if you used method (a) above; restart the machine!

Change the way you start WebDriver

To start WebDriver under the new headless scenario, you need to
  • First start ChromeDriverService and pass it the same DISPLAY value you set earlier.
  • Make sure that you pass it the location of your ChromeDriver executable.
    • This is shown below, using the CHROME_DRIVER variable.
  • Then start ChromeDriver as a RemoteWebDriver.
  • Pass it the URL of your ChromeDriverService.
Sounds complicated? Just copy the code below :)

//fix for headless systems:
// start service first
ChromeDriverService service = new ChromeDriverService.Builder()
.usingDriverExecutable(new File(CHROME_DRIVER))
.usingAnyFreePort()
.withEnvironment(ImmutableMap.of("DISPLAY",":10"))
.build();
service.start();
//then start driver, with URL mapped to above-started service URL
DesiredCapabilities dc = DesiredCapabilities.chrome();
ChromeOptions options = new ChromeOptions();
options.addArguments("--start-maximized");
dc.setCapability(ChromeOptions.CAPABILITY,options);
driver = new RemoteWebDriver(service.getUrl(), dc);

//stop chromedriverservice
driver.quit();
service.stop();

... And Done!

And that's it! Although slightly tricky, you should now be able to see some sorta-clean results from your Bamboo tests, with Selenium WebDriver.

Sources:

http://en.wikipedia.org/wiki/Xvfb
http://stackoverflow.com/questions/6183276/how-do-i-run-selenium-in-xvfb
https://sites.google.com/a/chromium.org/chromedriver/getting-started
Here's a related post, but this is for launching Google Chrome headlessly (not Selenium)
http://e-method.blogspot.fr/2010/11/google-chrome-with-xvfb-headless-server.html
http://stackoverflow.com/questions/7023942/running-chrome-webdriver-on-a-linux-server-with-no-display
https://code.google.com/p/selenium/issues/detail?id=2673

Monday, May 19, 2014

Moonrise - Timelapse

It was yet another sultry evening. I had settled down for some post-work channel-hopping. My camera, on the other hand had resigned itself to its fate - that of gathering dust in a corner of my closet. (which, of late, it has been doing a lot)

"Did you see the moon?" My wife pinged me. I went outside and was stunned by a blood red moon, reminiscent of karwa chauth, beginning to rise through the haze of the city. 

I've captured my share of moonshots before - but the last image I captured (that I'm reasonably happy with) was way back in March 2011; when the moon was 18% bigger owing to a happy occurrence called a perigee. 




I went back and resumed channel-hopping; then my wife asked me whether I took a picture of it. With half my mind on the sci-fi movie on Star Movies, I started thinking - this would be the perfect opportunity to restart (or at least try to) my buried hobby. Quite a lot of hobbies had been seeing layers of dust lately. Not any more, I decided, racing (and almost falling) to the closet and fishing all the gear out. 

By then the moon had risen enough to lose a bit of its redness. It was just about to vanish behind a cloud as I got off my first shots. 

And...they sucked.

I'd completely forgotten the Sunny F/16 rule (which I actually talked about recently to a friend of mine). So the images came out as a sort of very bright headlamp - nothing to tell that it was the moon.

By the time I got off any kind of shot with detail of the moon's surface, it had gone behind a cloud. And then, on some impulse, I decided to capture a timelapse. 

So - here's the result. Enjoy!

Sunday, May 18, 2014

Converting HTML to PDF on Linux

Wkhtmltopdf is a great tool to convert HTML files (and pages) to PDF.

You can retrieve the wkhtmltopdf tool using 
sudo apt-get install wkhtmltopdf

This wkhtmltopdf package on Linux though (tested on Ubuntu 12.10 Server edition) returns the following error:

This version of wkhtmltopdf is build against an unpatched version of QT, and does not support more then one input document

Sad! 

However, the patched version is available in http://wkhtmltopdf.org/downloads.html.

Here's how you install the patched version:

sudo apt-get remove --purge wkhtmltopdf



  • Then unzip the package, for e.g.
tar xvf wkhtmltox-linux-amd64*
  • Move that to a suitable location 
sudo mv wkhtmltox /usr/local/


  • Now you can run:
/usr/local/wkhtmltox/bin/./wkhtmltox

  • For further convenience, add the following to ~/.bashrc
export PATH=$PATH:/usr/local/wkhtmltox/bin

The First Post

It has happened to everyone.

You wish you were blogging again; but try as you might you never get down to doing it.

And then one fine day, you decide, to DO it. 

So, here's my attempt to DO it - to restart blogging. I actually have stuff to write about this time round; so here's to hoping I stick to my target.

Peace on Earth. (Ha!)