Showing posts with label windows. Show all posts
Showing posts with label windows. Show all posts

Wednesday, January 11, 2023

Accessing an SD Card on Windows from WSL2

Every so often I find that I want to access a Micro SD card from within Linux on my Windows WSL2 setup. I put the card in my laptop and am pleased when Windows announces the new D: drive is available. And then the confusion begins: how do I access this drive from a WSL2 Ubuntu session?

The C: drive is automatically available to me in /mnt/c; not so with D: and /mnt/d.

A Google search inevitably takes me this page which talks about using wsl --mount. While promising, this never works for me.

Often, by now, I've decided I didn't really need to access the SD card *that* badly and give up.

But, it turns out there's a trivial solution! The following mount command is all I need:

  sudo mount -t drvfs 'D:' /mnt/d

Once that command is issued, the SD card files are available under /mnt/d, and are accessible like any other files on Linux. This works for any drive letter, not just 'D'.

Notably, this isn't SD card specific. I just connected an Adafruit ESP32-S2 TFT Feather micro-controller to my laptop and Windows recognized it as a drive.

I issued the same mount command to make this visible to Linux:

$ ls /mnt/d
ls: cannot access '/mnt/d': No such device
$  sudo mount -t drvfs 'D:' /mnt/d
$ ls /mnt/d
'System Volume Information'   boot_out.txt   code.py   lib   settings.toml

And success! Now, if I can just remember to check the blog for this solution before I give up...

Friday, December 09, 2022

Laptop Trackpads Are Awful. AutoHotKey Makes Them Better.

Back in the day*, here's what a laptop trackpad looked like:

They had the familiar space to position your screen's cursor, and two physical buttons to send a left and right mouse click.

Fast forward today, here's what a modern trackpad looks like:

No doubt inspired by Apple's minimal aesthetic, the trackpad is now larger and the physical buttons have been removed. To left and right mouse click you simply press on the left or right hemisphere of the trackpad. Better, right?

In my experience, not so much. Because I keep my eyes on the screen and there's no tactile feedback as to the placement of my fingers, I find that I regularly click on the wrong side of the pad. Every time this happens I have to: (1) curse modern trackpads and (2) reset the position of my hand and click again. It's maddening.

To add insult to injury, some of the laptops I use require just a bit too much force to trigger a mouse-click. After a day of using these laptops, my digits are sore from all the pressing.

Thankfully, AutoHotKey can fix this mess. Here's how:

#SingleInstance,Force

;; 1. Make: Right Mouse Button send a Left Mouse Click
RButton::LButton

;; 2. Make: Win + Left Mouse Button send a Right Mouse Click
#LButton::RButton

;; 3. Make: Win + Right Mouse Button send a Right Mouse Click
#RButton::RButton

;; 4. Make: Win + Space send a Left Mouse Click
#Space::
MouseClick
return

;; 5. Make: Ctrl + Win + Space send a Right Mouse Click
^#Space::
MouseClick right
return

Commands (1), (2) and (3) re-work the logic of clicking on the trackpad. Now, any press anywhere on the trackpad sends a Left-Mouse-Click. And if I hold down Win and press the trackpad it always sends a Right-Mouse-Click. This removes the need to respect an imaginary boundary and makes all presses on the trackpad be consistent.

Commands (4) and (5) remove the need to press the trackpad at all by making the Win + Space send a left or right mouse click. Every time I click like this, I feel like I've traded a fatigue inducing task for a press on the o'l Easy Button.


*Photos courtesy of a recent laptop recycling effort.

Tuesday, February 15, 2022

Gotcha: When Windows and WSL2 Stop Talking To Each Other

When Linux and Windows Can Talk

Let's say I'm in an Ubuntu WSL2 session and I want to copy a file to Google Drive. The latest version of Google Drive allows access via a mounted drive, in my case, G:\. The simplest way I've found to copy a file into G:\ is to make use of PowerShell:

$ date > data.txt
$ powershell.exe -C copy "c:/Users/benji/Downloads/data.txt" "'G:\My Drive\'"
$ powershell.exe -C dir "'G:\My Drive\data.txt'"


    Directory: G:\My Drive


Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
------         2/14/2022   6:13 AM             29 data.txt


$ powershell.exe -C type "'G:\My Drive\data.txt'"
Mon Feb 14 06:13:56 EST 2022
$ cat data.txt
Mon Feb 14 06:13:56 EST 2022
$

While the commands above are clunky, they're straightfoward. I'm kicking off an instance of PowerShell from Linux to run simple Windows commands. Because those commands are running under Windows, they can trivially access G:.

Or, consider another example. Let's say I want to open a web page from emacs running under Ubuntu. In emacs, I evaluate the following lisp code:

(browse-url "https://blogbyben.com")

The result is that the URL is opened in the Windows version of Chrome. I didn't have configure this behavior, it Just Works. Until it doesn't.

When Linux and Windows Aren't On Speaking Terms

Every once in a while, I'll use the code above and I'll be greeted with a long delay and finally an error message. For example:

$ powershell.exe -C type "'G:\My Drive\data.txt'"
<3>init: (26039) ERROR: UtilAcceptVsock:244: accept4 failed 110

In emacs, running browse-url will occasionally just not work. Digging into the code, I realized that emacs is running xdg-open. Running this command manually, when the integration is broken, results in a similar error message.

$ time xdg-open https://www.blogbyben.com
<3>init: (26536) ERROR: UtilAcceptVsock:244: accept4 failed 110
<3>init: (26546) ERROR: UtilAcceptVsock:244: accept4 failed 110
<3>init: (26548) ERROR: UtilAcceptVsock:244: accept4 failed 110
<3>init: (26550) ERROR: UtilAcceptVsock:244: accept4 failed 110
/bin/xdg-open: 869: firefox: not found
/bin/xdg-open: 869: iceweasel: not found
/bin/xdg-open: 869: seamonkey: not found
/bin/xdg-open: 869: mozilla: not found
/bin/xdg-open: 869: epiphany: not found
/bin/xdg-open: 869: konqueror: not found
/bin/xdg-open: 869: chromium: not found
/bin/xdg-open: 869: chromium-browser: not found
/bin/xdg-open: 869: google-chrome: not found
<3>init: (26599) ERROR: UtilAcceptVsock:244: accept4 failed 110
<3>init: (26609) ERROR: UtilAcceptVsock:244: accept4 failed 110
<3>init: (26611) ERROR: UtilAcceptVsock:244: accept4 failed 110
<3>init: (26613) ERROR: UtilAcceptVsock:244: accept4 failed 110
/bin/xdg-open: 869: links2: not found
/bin/xdg-open: 869: elinks: not found
/bin/xdg-open: 869: links: not found
/bin/xdg-open: 869: lynx: not found
/bin/xdg-open: 869: w3m: not found
xdg-open: no method available for opening 'https://www.blogbyben.com'

real    1m20.400s
user    0m0.159s
sys     0m0.067s

What the Heck?

From poking around the web, I learned that the integration between Windows and WSL2 happens via the environment variable $WSL_INTEROP. This variable is set to a file system socket which provides the communication channel.

When I look in /var/run/WSL I see a number of files:

$ ls -1 /var/run/WSL/
10088_interop
11670_interop
13888_interop
1663_interop
17081_interop
1_interop
2189_interop
6106_interop
6268_interop
8855_interop
8_interop

The trick: WSL_INTEROP needs to be set to the right one for proper communication to happen. I confirmed this by going through the list of files, setting each one to WSL_INTEROP and seeing if that fixed the problem. Eventually it did. But trial and error is hardly the way I want to get back on track once Linux and Windows stop talking.

This discussion thread suggests using pstree to automatically set WSL_INTEROP. However the recipe provided didn't work for me.

The goal of the command appears to be to set WSL_INTEROP to the value /var/run/WSL/<PID>_interop where PID is the process ID of the parent's init process.

Looking at my running system, I see that init is running multiple times, and that the PIDs correspond to files in /var/run/WSL.

$ ps auxwww|grep init
root         1  0.0  0.0   1772  1100 ?        Sl   Feb09   0:00 /init
root         8  0.0  0.0   1752    80 ?        S    Feb09   0:00 /init
root      8854  0.0  0.0   1772   100 ?        Ss   Feb10   0:00 /init
root      8855  0.0  0.0   1780   100 ?        S    Feb10   0:01 /init
ben      26051  0.0  0.0   8160  2488 pts/6    S+   06:18   0:00 grep init

After a few attempts, I realized one way to get the correct init PID is to use the last one in the process list. Using grep, tail and awk I can extract this value.

$ ps auxwww|grep init | grep -v grep | tail -1
root      8855  0.0  0.0   1780   100 ?        S    Feb10   0:02 /init
$ ps auxwww|grep init | grep -v grep | tail -1 | awk '{print $2}'
8855

I then added the following to my .bashrc:

$ export WSL_INTEROP=/var/run/WSL/$(ps auxwwww|grep init | \
   grep -v grep | tail -1 | awk '{print $2}')_interop

This code properly sets WSL_INTEROP and revives the Windows / Linux communication channel. With that in place, I'm back to opening up web pages and PowerShell'ing like a champ.

Friday, June 11, 2021

Building Subversion from Source on WSL 2

I've officially transitioned from Cygwin to Ubuntu on WSL 2 as my preferred Unix environment on Windows. Cygwin performed well for decades, but WSL 2 is just too well done to pass up.

One feature of WSL 2 that I've been enjoying is the ability to easily build packages from source. This means that if the 'apt' version of the software is out of date, I'm not out of luck.

Two pieces of software that I find lag behind in package repositories are emacs and subversion.

After struggling to build emacs 27 on WSL 2, I found this handy build script that takes care of the process. What's the big deal, you ask? Download the package, run configure, make and make install. How hard is that? How about accounting for the the 137(!!) dependencies. That script is a life saver.

I couldn't find such a recipe for subversion. Besides tracking down dependencies, I often ran into another gotcha with subversion. Frequently, I'd build svn and forget to include the optional libserf library. In this case, Subversion compiles fine but then can't be used with 'https' URLs, which means it's effectively useless.

When I built subversion for a clean WSL 2 install yesterday I made sure to keep track the process. Here you go, enjoy.

Build & Install Subversion from Source on WSL 2

$ apt update -y
$ apt install -y autoconf libtool gcc  libsqlite3-dev libarchive-dev libz-dev libutf8proc-dev \
    libserf-dev libapr1-dev libaprutil1-dev libssl-dev  libmagic-dev liblz4-dev

$ wget https://mirrors.gigenet.com/apache/subversion/subversion-1.14.1.tar.gz
$ cd subversion-1.14.1
$ ./configure ; make
$ sudo make install

Thursday, February 04, 2021

Staying Close To Home: Using AutoHotKey to Jump Between Screens

Like most programmers, I've got two monitors and a dislike for taking my fingers off the home-row. On my Mac, I was inspired to write a Keyboard Maestro script to hot-key jump between my screens.

On Windows, I got by Alt-Tab'ing. I've got a new Windows box, and in the spirit of experimentation, I wondered if I could write a similar screen-jumping script for Windows.

The obvious choice was to build this in AutoHotKey. A quick Google Search turned up a similar request. A bit of MsgBox experimentation revealed that MouseGetPos returns positive x values for 'Monitor 1' and negative for 'Monitor 2'. SysGet can be used to determine the bounds of a monitor and MouseMove can be used to, well, move the mouse.

Once I had this figured out, I was able to build a version of a ScreenJump that hopped the mouse between the monitors. Version 1 plopped the mouse pointer in the center of the screen:

#SingleInstance,Force
CoordMode,Mouse,Screen

;; Windows+J does the jump
#j::
MouseGetPos,X,Y
If(X > 0) {
  SysGet, M, Monitor, 2
  Zone := -1
} else {
  SysGet, M, Monitor,1
  Zone := 1
}
CenterX := ((MRight - MLeft) / 2) * Zone
CenterY := (MBottom - MTop) / 2
MouseMove CenterX, CenterY, 0
return

After a few days, I refined the above function to capture the mouse coordinate and then jump to the other screen. I use these captured coordinates as the destination to jump back to, rather than always jumping to screen-center. Additionally, I added logic to give focus to the window the mouse lands on.

This function, triggered by Windows-j has become so embedded in muscle memory I can't help but wonder how I lived without.  Such is the joy of keyboard shortcuts.

#SingleInstance,Force
CoordMode,Mouse,Screen

ScreenJump() {
  MouseGetPos,X,Y
  global LastM1X, LastM1Y, LastM2X, LastM2Y

  ;; X > 0 is one screen, X < 0 is another screen
  ;; Before we jump away from the screen, capture our
  ;; 'last' coordinates so we can return there.
  If(X > 0) {
    LastM1X := X
    LastM1Y := Y
    SysGet, M, Monitor, 2
    Zone := -1
    LastX := LastM2X
    LastY := LastM2Y
  } else {
    LastM2X := X
    LastM2Y := Y
    SysGet, M, Monitor,1
    Zone := 1
    LastX := LastM1X
    LastY := LastM1Y
  }

  ;; Do we know our last position?
  ;; Great, jump there. If not, go to the center of the window.
  if(LastX != "") {
    CenterX := LastX
    CenterY := LastY
  } else {
    CenterX := ((MRight - MLeft) / 2) * Zone
    CenterY := (MBottom - MTop) / 2
  }

  ;; Give Focus to the window the Mouse is hovering over.
  MouseMove CenterX, CenterY
  MouseGetPos,,,GuideUnderCursor
  WinGetTitle, Title, ahk_id %GuideUnderCursor%
  WinActivate, %Title%
  return
}

#j:: ScreenJump()

Friday, January 15, 2021

PowerShell For the Win: Short and Sweet Edition

I just wrote this long and winding tale about how Windows PowerShell exceeded my expecations. Consider this post a sort of TL;DR for that monster.

I found myself needing to scale an image. On Linux and MacOS, I'd use the command line friendly ImageMagick. On Windows, I'd turn to Gimp. But I've got a shiny new tool in PowerShell, so I was curious if it could provide a Windows friendly command line solution. It does!

Step 1: I downloaded the Resize-Image Module from Microsoft's Technet Gallery.

Step 2: I launched PowerShell and typed:

## OK, let's do this...

PS C:\Users\benji\Downloads> Import-Module .\Resize-Image.psm1

# Wait, that worked? Oooh, cool! A quick look at the docs says that I
# can use -Display to preview image. Let me try that.

PS C:\Users\benji\Downloads> Resize-Image -InputFile .\icon.png -Height 200 -Display
Cancel

## The image was displayed, but smooshed

PS C:\Users\benji\Downloads> Resize-Image -InputFile .\icon.png -Height 200 -Width 200 -Display
Cancel

## The image is displayed and looks good. Let's make this official.

PS C:\Users\benji\Downloads> Resize-Image -InputFile .\icon.png -Height 200 -Width 200 -OutputFile icon.200x200.png

## That's it? No errors. No verbose out. I love it.

One gotcha: the OutputFile was stored in my hme directory, not the same directory as icon.png.

Step 3: I wrote this post.

I'm telling you, if you're a command line user and find yourself on Windows, PowerShell is your friend.

Wednesday, January 13, 2021

A Windows Friendly Solution to the Video Packaging Problem

A Windows Version, Please

A friend wanted to use my ffmpeg video packaging script. But there was a catch: he needed it to run on Windows. As a bash script, it runs seamlessly on Linux and MacOS, Windows not so mch.

The immediate solutions that came to mind weren't very helpful. I could re-write the script to be cloud based, but that would be terrifically inefficient in terms of both my time and complexity. I could have have my friend install a Unix environment on his Windows computer, but that seemed overkill for one script.

I could re-write my script in PHP and have my buddy install XAMPP. But again, that felt like swatting a fly with a sledge hammer.

The exercise I was going through was a familiar one. As a programmer, I'd been given an issue and I was making my way through the 5 stages of grief. Denial: nobody can expect me to make a Windows version of this script. Anger: why can't Windows be Linux! Bargaining: OK, you can have this script, but only if you turn your Windows box into a Linux box. Depression: ugh, Windows. And finally, acceptance: OK, so how would one do this on Windows?

Let's Do This!

I had an idea: I'd previously experimented PowerShell. And by experimented, I mean I launched the program, was excited to see it did tab completion and then quickly tired of using it. I'm sure it was step up from the standard DOS box, but it felt far inferior to the bash environment Cygwin provided. Still, PowerShell was a shell, so may be it had a scripting language I could use?

Finding sample PowerShell scripts was easy. At first I was overwhelmed: PowerShell was considerably more verbose than bash. But the ingredients for shell scripting were all there. After cursing Microsoft, it looked like PowerShell may be just the solution I was looking for.

Fast forward a couple of days, and I now have a version of my video packaging script that runs on Windows; no massive install of support tools required. Grab it here. You can run it by right mouse clicking on package.ps1 selecting Run with PowerShell. You can also launch the script within PowerShell using command line arguments. For example:

> .\package.ps1 -Source c:\Users\ben\Downloads\bigvid.mp4 `
                   -Config c:\Users\ben\Documents\blog\video-settings.ini

The Case for PowerShell

Here's a number of features of PowerShell that have left me more than a little impressed.

PowerShell let's me trivially launch a file picker, so that I can allow users to select the video and configuration file in a graphical way. It's also easy to confirm the files are valid and kick up a graphical alert box and if this isn't the case. In other words, PowerShell lets me do Windowsy things easily.

# Let users pick a file
function Prompt-File {
  param($Title, $Filter)
  
  $FileBrowser = New-Object System.Windows.Forms.OpenFileDialog -Property @{
    InitialDirectory = [Environment]::GetFolderPath('MyDocuments')
    Title = $Title
    Filter = $Filter
  }
  $null = $FileBrowser.ShowDialog()

  Return $FileBrowser.FileName
}

# Prompt for the video and config files
$Source = Prompt-File -Title  "Choose Video" -Filter 'MP4 (*.mp4)|*.mp4|QuickTime (*.mov)|*.mov|AVI (*.avi)|*.avi'
$Config = Prompt-File -Title  "Choose Settings File" -Filter 'INI File (*.ini)|*.ini|Text File (*.txt)|*.txt'

PowerShell doesn't natively support parsing ini files, but writing the code to do so was straightforward. I was able to adapt code on the web such that reading an ini file took in an existing set of values.

$settings = @{}
$settings = Parse-IniFile -File $PSScriptRoot\defaults.ini -Init $settings
$settings = Parse-IniFile -File $Config -Init $settings

Here I'm setting the variable $settings to an empty hashtable. I'm then filling the hashtable with the value of defaults.ini and then overriding these values with the user selected config file. This is similar to the variable overriding I did in the Unix version of the script, though arguably it's cleaner.

I was able to move my custom code to separate files in lib directory, thereby keeping the main script readable.

I'm impressed how PowerShell natively handles named parameters. Not only can I define functions with named parameters, but I was able to have the script itself trivially take in optional parameters. At the top of package.ps1, I have:

param($Source, $Config, $SkipFcsGeneration)

I then check to see if -Source or -Config was passed in. If either are missing, Prompt-File is invoked.

if(-not $Source -Or (-not $(Test-Path $Source))) {
  $Source = Prompt-File -Title  "Choose Video" -Filter 'MP4 (*.mp4)|*.mp4|QuickTime (*.mov)|*.mov|AVI (*.avi)|*.avi'
}

if(-not $Config -Or (-not $(Test-Path $Config))) {
  $Config = Prompt-File -Title  "Choose Settings File" -Filter 'INI File (*.ini)|*.ini|Text File (*.txt)|*.txt'
}

These named parameters let me treat my script as a command line tool, and let's a typical Windows user think of it as graphical app.

I also improved the packaged video file itself. I added support for playing audio over the 'pre' title screen. The script uses ffprobe -show_streams to figure out the length of the audio clip and arranges for the title screen to be shown for this duration. PowerShell let me trivially kick off ffprobe and process its output, just like I would do in a bash environment.

Working with the audio stream forced me to understand how ffmpeg's complex filters worked when both video and audio streams are being manipulated. My Aha Moment came when I realized that you filter audio and video separately and that ffmpeg will piece them together. Essentially, I have one concat expression to join all the video streams and one to join the audio streams, and ffmpeg does the right thing to combine the audio and video at the end.

A Happy Ending

I finished this little project with not just a video packaging script that works on Windows, but with a fresh perspective on Windows scripting. What PowerShell lacks in tradition and terseness, it more than makes up for in capability and completeness. In short, PowerShell isn't an attempt to implement bash on Winows; it's a fresh and modern take on scripting that really delivers.

Check out my video packaging code over at github.

Friday, April 12, 2019

The Embarrassingly Simple Source for An Up To Date Windows Version of emacs

I recently replaced my no-name mini PC with a might-as-well-be-no-name Kingdel NC860 mini PC. These fanless desktop computers have a great form factor, dual monitor support, plenty of USB ports and a bare-bones feel that I love. Credit goes to Coding Horror for inspiring my first purchase of this type of device.

I've recently switched from Firefox to Chrome as my primary browser of choice, and 1password as my password manager. The result: installing Chrome and logging in using both my Work and Personal e-mail meant that my web-based life was essentially setup. Installing Cygwin, Gimp and AutoHotKey meant that I had a nearly complete dev environment. All that was left to do was to install emacs.

At this point, I usually Google around to find the latest version of Windows friendly emacs, often ending up on this sourceforge site. On a whim, however, I thought I'd try something different: I installed emacs via cygwin.

My expectation was that I'd get a console only emacs. And my assumption was totally wrong. I ended up with the same Windows friendly emacs I'm used to, except a whole slew of issues had been resolved. I'm used to emacs operating in terms of Windows drive paths, while cygwin works in terms of a unix'y path mapping. By using a cygwin based emacs, the two environments are now in sync.

A number of issues with eshell were magically fixed, too. #! detection and signal handling (hitting Control-c) in eshell wasn't reliable in my old Windows emacs setup, whereas it's working well under cygwin based emacs.

Finally, the cygwin version of emacs is as up to date as the GNU site offers: version 26.1.

Why didn't I try this years ago?

It blows my mind that I can go from new PC to working dev environment in 15 minutes and zero dollars spent on software.

Friday, April 05, 2019

Dusting off my Microsoft Word Programming Skills

When it comes to programmatically working with documents, I'm all about Google Docs, Sheets and Slides. But I recently found myself needing to step over to the Dark Side and write some code to interact with Microsoft Word. After a quick Visual Basic for Applications (VBA) refresher, I managed to eek out the code I needed. Surprisingly, I found myself impressed by the capabilities VBA offers. The API is massive and appears to let you automate nearly everything related to Word Docs. I suppose being an ancient and archaic incredibly mature product has its benefits.

In the interest of leaving breadcrumbs for myself, or for helping some other soul who finds themselves needing to write some Word VBA code, I'm publishing a bit of test code I wrote while tackling my project. It's CS 101 level stuff: iterate through the currently selected table and display the max and min numeric values found. But given how rusty my VBA skills are, working through this example was quite helpful.

Here's the code:

Sub TableInfo()
    minVal = 0
    maxValue = 0
    
    Dim t As Table
    If Selection.Tables.Count > 0 Then
        Set t = Selection.Tables(1)
        t.Range.Copy
        For Each r In t.Rows

            For Each c In r.Cells
                v = Left$(c, Len(c) - 2)
                v = Replace(v, "$", "")
                v = Replace(v, ",", "")
                If IsNumeric(v) Then
                    If Val(v) > maxValue Then
                        maxValue = Val(v)
                    End If
                    If minValue = 0 Or Val(v) < minValue Then
                        minValue = Val(v)
                    End If
                End If
                    
            Next
        Next
        MsgBox "Min: $" & Format(minValue, "##,##") & ", Max: $" & Format(maxValue, "##,##")
    Else
        MsgBox "You're not currently on a table."
    End If
End Sub

And here's a screenshot, note the new toolbar item I added:

You can download the Word file containing the code here. Happy Hacking!

Tuesday, February 28, 2017

Way Overdue Review: A Fanless Windows Mini-PC

As I went to blog about my new Mac-Mini, I searched for a post describing the Windows computer that would be the Mini's neighbor. To my surprise, I apparently never got around to blogging about it. And it's not because the Windows computer was forgettable, on the contrary, it's quite a zippy little machine deserving of a review. So here it is, better late than never.

First off, I got the idea for purchasing what amounts to a Windows-Mini computer from this post: The Scooter Computer. Like all nearly everything on CodingHorror.com, it's impeccably researched and smartly written. While the use case for the computer in that article was markedly different than what I was after, I was intrigued enough to learn more.

What finally got me to hit the buy button was the form factor of the computer. For years, I'd purchased laptop after laptop. And while I still do occasionally need both the power and portability of a laptop, most days I can make do with either. That is, I'm either in front of my dual monitor setup (power!); or hacking away on my portable keyboard and cell phone (portability!). Better to invest the money in processing power and RAM than purchasing a slick laptop which would serve to do little more than power my existing monitors and keyboard.

I started my purchase process on a page much like this one:

(Note: the above screenshot shows a computer with one HDMI port and one VGA port. My model has two HDMI ports.)

I recall being psyched I could deck the machine out with 16GB of RAM, something that was unusual to find in a laptop. I also choose to up the other specs as much as possible and hit the buy button. At around $460, I figured I might as well splurge.

A few weeks later, my Mini-PC showed up. That was back in February of 2016. Here's how the computer looks now:

(The silver case is the laptop, the black rectangular device next to it is an external hard drive. Not shown is the beautiful traditional Chinese Knot, represents good luck always with you, that the listing promised and was indeed delivered with the computer.)

I'd never purchased anything on Ali Express, so I didn't really know what to expect. To my delight, the customer service was quite good, with the seller answering many of questions and offering to tweak the order as I needed it. This was the first time I'd purchased a non-major brand PC, and given my experience I'd gladly do it again.

After about a year of use, here's what I can say I like about it:

  • The computer is silent. The Mac-Mini makes this tiny tinkling noise, which I'd never notice, except I was putting it next to an even quieter computer. Gone are the days of loud laptop fans preparing for take off. Whoo!
  • The device is USB port heaven - there are 4 ports on the front and 4 ports on the back. Again, compared to my Mac-Mini, this thing is luxurious.
  • The SD Card slot is a win for me, as is the dual HDMI ports
  • It (Mostly -- see below) Just Works. Windows runs well on this bad boy, and most days I don't have to think about hardware, which is sort of the point.

And after a year of use, here's what I don't like about the device:

  • At times I've struggled with mouse stuttering and lag. I've tried replacing batteries and mice. I've tried USB vs. Bluetooth. These days it's not a huge problem, but occasionally the problem strikes again. I always assumed it was due to limited processing power. Though, I've struggled with the same exact issue on my Mac-Mini. Mice are awful.
  • The 256GB SSD is fast and all, but its capacity is tiny. In just a few months I managed to fill it up. As a quick fix, I was able to plug in a random 1TB USB hard drive which I had lying around. However, file access times can be horrendously slow with the derive. I've found that I basically use the external drive as a sort of swap space, storing less used projects and files there. At some point, I really do need to fix this and upgrade *something*. For years, hard drives kept getting bigger and bigger, so I never gave a second thought to them. With these tiny SSD drives, it's back to being an issue.
  • Webcam, Microphone and Speaker not included. Well, duh. Ultimately, the fact that the computer needs an external camera, mic and speaker is probably a good thing, as these accessories are cheap and easy enough to upgrade. But if you do plan to take the computer out of the box and use it on a Skype call, don't be surprised when you're not able to fully participate.

After nearly a year of use, I can say that this no-name Mini-PC is really working out quite well. When it's time to retire this bad boy (probably at the end of the year), I'll probably opt to purchase a similar one as a replacement.

Incidentally, if you're looking for a computer that you can just use for surfing, checking mail and watching YouTube, here's a better option: ChromeBit. But if you want to do actual computing, this device should have you covered.

Friday, December 23, 2016

Week one of being an Apple Fanboy | Choosing a Mac as a Primary Work Computer

OK, I'm not quite sure I'd call myself an Apple Fanboy just yet (as I'm typing this post on a Microsoft Ergonomic Keyboard). However, I have phased an OS X Mac-Mini into my work environment. Recall that since 2009 I've had a simple strategy for hardware risk management: I have two work computers, and every Monday and I switch the one in use for the week. The result is that if I have a hardware failure, I can effortlessly switch to the backup.

For years, this meant running two Windows Laptops. Then I switched the arrangement to a Windows Laptop paired with a Linux Laptop. Then it was a Linux Laptop paired with a compact Windows Desktop. And now it's official: for 2017 I plan to use a Mac-Mini paired with a similar footprint Windows Desktop Machine. Here's the new setup:

Attentive readers will note that this isn't my first time owning a Mac-Mini. I've had one on the side now for about 5 years, but I ran it just when I needed to do something OS X specific, which wasn't all that often.

So it's been essentially a week in Apple land, and so far, the biggest news is that there is no news. Most things Just Work. My core tools: emacs, bash, subversion, Firefox, etc. all work just as seamlessly on OS X as they did in Windows and Linux. It's mind blowing that I can take a totally new computer and OS, use subversion to pull down my configuration and within a few minutes be working away like this was a well-worn environment.

Consider this win: I've got a relatively fancy script I use to setup ssh tunneling. I checked it out of my subversion tools repository and ran it. And just like that, it worked. Of course, it's just standard bash commands, but still, to have something relatively complex like this not require changes is just too cool.

There's really not much to report on the hardware side. Yes, the Mac-Mini is sexier than the boxy Windows computer next to it. And at at $650 or so bucks, it's relatively cheap. But the Windows box is fan-less, so it's absolutely silent, whereas the Mac-Mini makes these tiny tinkling noises. Hardly a deal breaker, but it is noticeable. The Windows box also has far more USB ports, some of which are conveniently placed on the front panel. But still, these are minor points and from a hardware perspective the Mini gets a passing grade.

On the other hand, OS X as an operating system definitely shows promise. It has that mainstream vibe that Windows has: so tools like Skype work in a polished way out of the box. *And*, it also has a strong geek vibe: I don't need to layer Unix tools on top of the OS via Cygwin, instead they are native. At one point I struggled to get ssh-agent working properly, only to realize that it was my hacky Windows setup that was breaking things. Mac apparently supports ssh-agent out of the box and I just needed to move all that crap out of the way and let it just be.

I've warmed to a number of OS X design choices quite quickly. For example, both Windows and Mac support multiple desktops, but they have one noticeable difference with respect to how multiple monitors are handled. On Windows, when I 'slide to the desktop on the left' both monitors change. On Mac, only the current monitor changes. This allows for mixing and matching of desktops. At first this was jarring, but I think it's a win.

The biggest source of pain for the last week has been the mental grinding of gears needed to adapt to new keyboard shortcuts. Consider this keyboard conundrum: on Windows, Control + a selects all the text, and the Home key takes you to the beginning of the line. On Mac, Control + a takes you to the beginning of the line, whereas Home takes you to the start of the text field you're in. So for the last week, I've been hitting Home to get back to the start of the line, and then get annoyed because I've totally lost my place and typing at the start of the document. I've tried some recipes for overriding this behavior, but ultimately, I'm just going to need to retrain my brain.

The irony is not lost on me that years ago, when I switched from Linux to Windows, I had to stop using Control + a to get to the start of a line, only to now have to relearn this habit.

Fortunately, the keybindings in emacs are universal. So for most of my coding tasks, the keyboard short-cut changes haven't been all that big an issue.

I'd consider this first week of being a Mac Guy a solid success. My brain is going to be pissed when it finds itself on a Windows environment next week, but it'll deal. For now, I'm just happy that I can be following Rule #1 for business computing hardware: Diversify

Any killer Mac utilities or features I should know about? I need an autohotkey replacement, which I think will be AppleScript. But maybe you've got a better idea; I'd love to hear it!

Thursday, August 25, 2016

An Emacs Friendly Caps Lock Configuration on Windows

While this may be obvious, I was pretty dang pleased with myself when I managed to turn the Caps Lock key on my Windows 10 computer into an emacs friendly Hyper key. Here's what I did:

Step 1. Use AutoHotKey to trivially map the Caps Lock key to the Windows Menu key, or as AutoHotKey calls it, the AppsKey.

;; Add this to your standard AutoHotKey configuration
CapsLock::AppsKey

Step 2. Use this elisp code to capture the Menu key from within emacs and map it to the Hyper modifier:

;; http://ergoemacs.org/emacs/emacs_hyper_super_keys.html
(setq w32-pass-apps-to-system nil)
(setq w32-apps-modifier 'hyper) ; Menu/App key

Step 3. Enjoy! I can now map any key binding using the H- modifier. Here's some code I added to my PHP setup:

(defun bs-php-mode-hook ()
  (local-set-key '[backtab] 'indent-relative)
  (local-set-key (kbd "<H-left>") 'beginning-of-defun)
  (local-set-key (kbd "<H-right>") 'end-of-defun)
  (auto-complete-mode t)
  (require 'ac-php)
  (setq ac-sources  '(ac-source-php ))
  (yas-global-mode 1)
  (setq indent-tabs-mode nil)
  (setq php-template-compatibility nil)
  (setq c-basic-offset 2))

The result: when I open up a PHP file, I can jump between function definitions by holding down Caps Lock and left or right arrow.

I feel like I just won the keyboard shortcut lottery!

Wednesday, January 06, 2016

Windows 10: Keyboard Shortcuts Worth Knowing

A few weeks back, after much nagging, I upgraded my Windows 8 laptop to Windows 10. The results so far have been pretty underwhelming. For the most part, not much has changed. The most noticeable difference is that logging in has turned into an infuriatingly slow process. Just selecting and typing in my password seems to take an exorbitant amount of time. From there, it seems to take additional time just to get to a desktop where I can start opening windows and getting to work. It's probably just a few seconds here or there being added, but it's definitely painful. Oh, and I find that Firefox has been doing the Unresponsive thing more often. Of course this is all anecdotal, and who knows, maybe Windows 10 is doing some awesome stuff behind the scenes.

Anyway, there is one bright spot in Windows 10. It seems that they've taken the keyboard shortcuts started in Windows 8 and dressed them up a bit more. It's not Ratpoison, but it's an improvement.

The change I tripped over was that now Windows-Left and Windows-Right allow for stacking of Windows, not just side-by-side placement. I use two stacked monitors, so I'm not sure how urgently I need this, but it's good to see improvements being made in keyboard shortcuts. After reading up on this new Left/Right change, I learned that Windows 10 has a number of new key shortcuts worth learning. Including:

  • Windows + Tab - an interactive version of Alt-Tab. This gets points because you can navigate the windows via the cursor keys. It loses points because there's no way to traverse both monitor's worth of windows. It strikes me as strange that you would design keyboard shortcuts in this day and age and not take into account multiple screens.
  • Windows + Shift + D - create a new virtual desktop. Whoo! Windows 10 has finally caught up to FVWM circa 1995. Kidding aside, virtual desktops are a nice edition.
  • Windows + Shift + Left, Windows + Shift + Left - traverse the virtual desktops.
  • Windows + X - open up the Start Menu. I learned about this gem here. I'm not sure why I'd need to get back to the good old Start Menu, but it's good to know it's there.
  • Windows + Print Screen - grabs a screenshot, saves it as a file. Well that only took them like 10 years to add to Windows, yeesh. The traditional method of capturing the screen and storing it on the clipboard may have been handy for programmers, but what a pain for regular users.

Finally, here's a cheat sheet to all of Windows 10 keyboard short cuts, courtesy of LifeHacker:

Wednesday, August 26, 2015

Lumia 640 Windows Phone Experience - The Bad and Reality

While I found quite a few nuances of the Lumia 640 running Windows Phone OS enjoyable, my little experiment has wound down. I'm back to using my Galaxy S5 and Android. I feel like I owe the hard working developers at Windows an explanation.

1. I heavily rely on the Google Platform, so having a Google Phone just makes sense. I'm all about Gmail, Google Docs, Google Keep, heck even YouTube. While the Windows Phone makes an attempt to fill some of these needs (adding a Google Gmail account, even one with 2 factor authentication, was easily enough), the tools just aren't as polished as they are on Android. The mail app has no quick way of archiving messages, or the ability to leverage the Priority inbox. I found a Google Docs app, but it doesn't allow editing of docs, which is essential for me. And the YouTube app is little more than a thin web wrapper around the mobile site. Sure, it's functional, but not polished.

2. The system lacks the geek factor that I require. My first impression was that the apps I needed were just not there on Windows, but after a few days of use, I realized that's an unfair generalization. Apps like Overdrive and Tapatalk were quite functional in the Windows Phone world, and ones like Run the Map and AudioCloud were quite good. No, it's the esoteric stuff that's not up to par yet.

For example, the phone pairs with my Bluetooth keyboard, but without a program like EHK, there's no way to remap keys or make shortcuts work. Sure, there's an ssh program, but not one that I could get bash style keybindings working with. And while the phone had things like quiet hours that were intuitive to use, there's no sign of a Tasker type app that let's customize the phone in unusual ways. Or better yet, allow me to prototype apps with ease. I was excited to find a Scheme implementation on Windows, but it's toy when compared to the one on Android.

Of course, for most Windows Phone users, these features are esoteric and unnecessary. And further more, over time, Windows will probably get there. But for now, I can turn my Android phone into a little dev laptop and I can't say that about my Windows phone. Also, if I was a Microsoft Office guy, I wonder if using the Windows Phone would have felt like coming home? But having an aversion to Word and Excel, I couldn't even bring myself to experiment with these on the phone.

Oh, and the camera quality was blah on the phone. Though, being a relatively low cost phone, that's no big surprise.

So back to the Android universe I go.

But if I'm a core Android developer, I'm not patting myself on the back quite yet. Microsoft has a habit of getting things right on their Nth try, and the Windows Phone I played with was actually quite usable. Ignore it at your own risk.

Wednesday, March 04, 2015

wine: The 'but I have to run Windows' excuse killer for Linux

When I switched one of my work laptops to Linux, I assumed that I'd run into a handful of Windows-only apps that would make the switch untenable. This hasn't turned out to be the case. The majority of Windows requirements were addressed through web based apps and standard Linux based tools. For more obscure replacements (say, Picsa image management), I was able to find a Linux variant that worked as well (say, shotwell). More importantly, I'm finding the scriptable philosophy of Linux is making me even more productive than I was on Windows.

That's not to say that I haven't run into a couple of Windows specific challenges. To my delight, I've found wine is a lightweight, almost invisible solution, to this problem.

Two apps that I've run under wine include an ancient evoice audio player (needed to playback audio files from a free evoice account I use) and Anyplace Control's desktop viewing software.

The evoice player was my first foray into running a Windows app under Linux. It was more proof of concept than anything else, as the alternative was replacing the ancient evoice account with a more modern (though probably not free) solution. Using wine couldn't have been easier: I simply downloaded the .exe file and then ran: wine evoicesetup.exe. And to my amazement, the Windows installation popped up. Once the process was done evoice was installed, though it wasn't immediately obvious where.

Now, when I want to play back an evoice file I run this script:

#!/bin/bash

## A wrapper around the evoice windows app

if [ ! -f "$1" ] ; then
  echo "Usage: `basename $0` file.evc"
  exit
fi

wine 'c:\\Program Files (x86)\\eVoice Player 1.0\\eVoicePlayer.exe' $1

I derived that path by looking under ~/.wine/drive_c and converted it to the standard Windows format.

Admittedly, The above setup isn't perfect. When I run evoice under wine my xterm is filled with warning message like so:

But it plays back the audio just fine. And given how often I have to play these files, this solution is perfect.

Anyplace Control, a type of Remote Desktop software that one of my clients use, seemed like a taller order for wine. I ran wine AnyplaceControlInstall.exe and was greeted with this promising dialog:

I hit Next but nothing happened. After much experimentation I realized that the install was kicking off a bunch of windows and ratpoison was probably not displaying them properly. To address this I kicked off Xnest with the following script:

#!/bin/bash

##
## Kick off a full screen Xnest session
##
geom=`xwininfo -root | grep geometry`
target=:10

Xnest $target  $geom &
sleep 1

export DISPLAY=$target
twm &
xsetroot -solid SteelBlue
xterm &

This started up the Very Old School twm as its own little desktop universe. The install ran just fine from there.

To run Anyplace Control, I simply kick off an Xnest session like above and then run:

  wine 'c:\\Program Files (x86)\\Anyplace Control\\apc_Admin.exe'

Turns out, Anyplace Control runs even better than evoice, with far fewer warning messages. In fact it runs just as well on my Linux box, if not better, than it does on my Windows box.

I had no idea wine was such an unsung hero on Linux. It's a project that's been around forever (well, since 1993!) and effortelssly knocked down some of the trickiest hurdles to a Linux only lifestyle. Well done!

Monday, February 09, 2015

Typing on Windows, Making Magic on Linux - Adventures in Linux Desktop Remote Control

I always keep two laptops in rotation and every Monday I toggle back and forth between the one I'm using. At the moment, one runs Windows, the other Linux. Last week I spent on the Linux box, this week it's Windows time.

As an aside, one of the trickiest aspects of setting up Linux was learning that my touchpad wasn't properly recognized (it needed to be marked as a touchpad). Basic operations like dragging windows and selecting text wouldn't work; it was terrifically frustrating. This morning, like an attention seeking toddler, my Windows box decided that it too should have touchpad problems. It stopped working, and caused the keyboard to go haywire. I've since turned off the trackpad, and the system appears to be functioning. Guess Windows isn't always the Just Works OS I claimed it was.

OK, back to the task at hand.

I'm typing on my Windows box, but my Linux box is right next to it. I can reach over, and type plsplayer http://somafm.com/bootliquor.pls to start playing some background music. But this is Linux, surely I can do better than that? Of course I can.

The first order of business was to get the ssh daemon running on my Linux box. This turned out to be easily done with these commands. Next I needed to figure out how I was going to learn the IP address of my Linux box. I was imagining the need to run some sort of internal DNS. Turns out, it's far easier than that. My router, upon giving my Linux box its IP address, noted its hostname. To my complete shock and amazement I can do:

  ssh longshot ls

Where 'longshot' is the name I gave my Linux box. It just works. I knew the router provided DNS services, I never imagined it was smart enough to incorporate names from DHCP into this service. Note to self: don't underestimate the router.

OK, so I can now ssh to my Linux box. This is good. But it gets better: because I'm relying ratpoison as my window manager, and because ratpoison is scriptable I can have it switch windows around remotely with great ease. I just type:

  ssh longshot DISPLAY=:0.0 ratpoison -c next

I can trivially kick of my voice commands, too:

 ssh longshot saytime

By default, Firefox will open up the URL handed to it within currently focused instance. So the following opens up CNN in the current browser:

 ssh longshot DISPLAY=:0.0 firefox http://cnn.com 

And for complete control, I can use xdotool to simulate mouse and cursor events. I can bring up a word definition by using the following set of commands:

ssh longshot DISPLAY=:0.0 firefox http://google.com 
ssh longshot DISPLAY=:0.0 xdotool key Return
ssh longshot DISPLAY=:0.0 xdotool type definition
ssh longshot DISPLAY=:0.0 xdotool key space
ssh longshot DISPLAY=:0.0 xdotool type their
ssh longshot DISPLAY=:0.0 xdotool key Return

Or, perhaps I want to bring up cnn.com and then page down the screen:

( ssh longshot DISPLAY=:0.0 firefox http://cnn.com ;
  sleep 5 ;
  ssh longshot DISPLAY=:0.0 xdotool key Next ;
  sleep 5
  ssh longshot DISPLAY=:0.0 xdotool key Next  )

Note that xdotool type sends text, whereas xdotool key sends a particular key.

Of course, typing all this in by hand is silly. I whipped up a quick shell script which I run from Cygwin on my Windows box. There's almost certainly some fancy keyboard / screen sharing solution I could setup between these laptops. But that's not really what I'm after. I love exploiting the power of the command line, and using the above tools I can do that even if the command line I'm typing at happens to be on a different computer.

Note: none of the above commands X-related commands will work for you unless you do something like xhost + in your X-session. You almost certainly don't want to do this, though, a it allows anyone on your local network to broadcast something to your screen. Back in college, if a TA or perhaps even professor happened to do this, we'd make them pay by doing something like:

  xv -display 192.183.282.47:0.0 -root foo.jpg

and in the middle of their lecture and foo.jpg would become their new background. Yeah, not something you want to allow a bunch of kids in a lab, two buildings away, to be able to do.

Anyway, think through how you want to share access to your screen before you do.

Here's that script:

#!/bin/bash

##
## Run commands on my Linux box who happens to be named
## longshot.
##

HOST=longshot
DISPLAY=:0.0
RP=ratpoison
cmds="saytime|sayforecaste|xeyes|nextwin|prevwin|ff|pgdn|pgup|sh|vol"
cmd=$1 ; shift
expr2=''

case $cmd in
  saytime)
    expr="$cmd" ;;
  sayforecast)
    expr="$cmd" ;;
  xeyes)
    expr="$cmd" ;;
  nextwin)
    expr="$RP -c 'next'" ;;
  prevwin)
    expr="$RP -c 'next'" ;;
  ff)
    expr="firefox $@" ;;
  pgdn)
    expr="xdotool key Next" ;;
  pgup)
    expr="xdotool key Prior" ;;
  sh)
    expr="$@" ;;
  vol)
    expr="amixer set Master $1" ;;
  *)
    echo "Usage: `basename $0` {$cmds}"
    exit ;;
esac

ssh $HOST DISPLAY=$DISPLAY $expr

if [ -n "$expr2" ] ; then
  ssh $HOST DISPLAY=$DISPLAY $expr2
fi

Thursday, July 03, 2014

Text to Speech from the Command Line On Windows in 6 Lines of Code

Back in the day, compiling a Linux kernel would take hours. To make life easier, I'd kick off the task and have it notify me when it was finished. Something like so:

 make ; notify "Whooo! We're done!"

When I was really feeling fancy, I'd setup notify to invoke an X10 controller, causing a light to blink on and off. That way, I wouldn't have to be in the room to know the task was done.

These days I'm on Windows, the Linux kernel can be (depending on your hardware and configuration) compiled in minutes, and I don't have X10 setup any longer (note to self: why the heck not?!). But, that doesn't mean I don't have long running tasks I need to kick off from the command line.

One possible solution: use a text to speech command to let me know a task is done. It's not as attention getting as flashing a light in another room, but it's still a solid form of notification. Looking around, it didn't appear as though there was a standard command line tool on Windows for doing text to speech. So, using this AutoHotKey example, I wrote my own.

;;
;; Simple text to speech command line tool. Inspired by:
;; http://www.autohotkey.com/board/topic/77686-text-to-speech-examples-for-autohotkey-v11/
;;
;; Usage: speak.ahk [words ...]
;;
engine := ComObjCreate("SAPI.SpVoice")

buffer := ""

Loop %0%
{
  word := %A_Index%
  buffer := buffer . " " . word
}

engine.Speak(buffer)

Now I can say:

  make ; speak "Compilation is finished."

and my computer will announce when the make command is done.

I'm telling you, AutoHotKey never ceases to amaze.

Wednesday, May 14, 2014

Gotcha of the Day: Adobe AIR adt fails with "Unable to run aapt"

This morning I installed the Adobe AIR SDK on one my laptops that had never been used for Air development. I kicked off adt.bat from within Cygwin and was greeted with the following:

$ adt.bat -package ...
unexpected failure: Unable to run aapt
java.io.IOException: Unable to run aapt
        at com.adobe.air.apk.APKOutputStream.generateResourcesAndManifest(APKOutputStream.java:842)
        at com.adobe.air.apk.APKOutputStream.addApplicationDescriptor(APKOutputStream.java:298)
        at com.adobe.air.ApplicationPackager.addSpecialFiles(ApplicationPackager.java:301)
        at com.adobe.air.ApplicationPackager.createPackage(ApplicationPackager.java:66)
        at com.adobe.air.ADT.parseArgsAndGo(ADT.java:590)
        at com.adobe.air.ADT.run(ADT.java:435)
        at com.adobe.air.ADT.main(ADT.java:485)

What the heck?!

I tried a number of obvious'ish things: I tried an older version of the SDK as well as an x86 version of the Java JRE. Nothing.

Even Google failed me, offering no useful hits for this exception. It was time to get creative.

Step one, I decided I'd take a peek at the source code for APKOutputStream. I downloaded a very slick Java Decompiler (back in the day, jad was an aboslutely essential tool for Java development) and was trivially able to find the function in question:

private void generateResourcesAndManifest(String packageName, ApplicationDescriptor descriptor, int appVersionCode)
    throws IOException
  {
    String pathToApptTool = getAapt().getPath();
    ...
    try
    {
      ProcessBuilder pb = new ProcessBuilder(aaptCommand);
      Process p = pb.start();
      ByteArrayOutputStream aaptOutput = new ByteArrayOutputStream();
      new Utils.OutputEater(p.getErrorStream(), aaptOutput).start();
      new Utils.OutputEater(p.getInputStream()).start();
      p.waitFor();
      if (p.exitValue() != 0) {
        aaptOutputString = new String(aaptOutput.toByteArray(), "UTF-8");
      }
    }
    catch (Exception e)
    {
      throw new IOException("Unable to run aapt");
    }

The source gave me a couple of leads, including mention of the following command line option:

  aaptCommand.add("--target-sdk-version");
  aaptCommand.add("17");

I checked and realized I didn't have version 17 of the Android SDK installed. However, installing that package still didn't help.

Next up, I decided I dig even deeper and run adb through JDB. This turned out to be pretty straightforward. I kicked off jdb and entered the following commands:

$ jdb
stop in com.adobe.air.apk.APKOutputStream.getAapt
stop at com.adobe.air.apk.APKOutputStream:845
run  -jar c:\tools\flex4\lib\adt.jar -package ...

While on line 845, I inspected the variable pathToApptTool and learned that adb was trying to execute:

 c:\tools\flex4\lib\android\bin\aapt.exe

From within Cygwin I tried to manually execute that command. Thankfully, I got a permission denied error. Finally, a problem I knew how to fix! I ran:

 $ cd /cygdrive/c/tools/flex4
 $ find -name '*.exe' -exec grep chmod a+rx {} \;

And sure enough, the adt command finally ran without fault.

In the end, this issue had nothing to do with Adobe AIR and everything to do with Cygwin and Windows permissions. Still, check out the exception handling in the above code; it's atrocious. The only code that would have been worse would have been to swallow the exception. Still, to catch a detailed exception and not report it is a rookie mistake. It would have cost them no additional effort to output the root cause of the exception as well as the full command line that failed.

Seriously, Adobe, you can do better. At least, I certainly hope you can..

Wednesday, April 30, 2014

Gotcha of the Day: ffmpeg converted flv file won't play in Windows Media Player

I'm using ffmpeg to convert flv files to avi files. The goal is to generate videos that any old version of Windows Media Player can play back.

I was using the command:

  ffmpeg -i foo.flv -codec:v mpeg4 -flags:v +qscale \
    -global_quality:v 0 -codec:a libmp3lame \
    foo.avi

And while the video played fine on my machine and using VLC, there were some Windows computers where Windows Media Player would kick back a useless error message, and refused to play the file video.

My first thought was that the codec was to blame. Using ffprobe I was able to find out the codec:

 $ ffprobe foo.avi
 ffprobe version N-62756-g2cf5143 Copyright (c) 2007-2014 the FFmpeg developers
 ...many lines trimmed...
 Stream #0:0: Video: mpeg4 (Simple Profile) (FMP4 / 0x34504D46), yuv420p, 320x230 [SAR 1:1 DAR 32:23], 1k tbr, 1k tbn, 1k tbc
 Stream #0:1: Audio: mp3 (U[0][0][0] / 0x0055), 44100 Hz, mono, s16p, 64 kb/s

But alas, I had videos encoded with FMP4 that played back fine on the same computers that were choking on this newly generated video.

For the heck of it, I ran a file against the generated avi file:

foo.avi: RIFF (little-endian) data, AVI320 230 >30 fps, video: FFMpeg MPEG-4, audio: MPEG-1 Layer 3 (mono, 44100 Hz)

Whoa, that >30 fps is suspicious.

A quick check of the ffmpeg docs told me about -r:

‘-r[:stream_specifier] fps (input/output,per-stream)`
Set frame rate (Hz value, fraction or abbreviation).

As an input option, ignore any timestamps stored in the file and instead generate timestamps assuming constant frame rate fps.

As an output option, duplicate or drop input frames to achieve constant output frame rate fps.

This looked promising!

Indeed, I ran the exact same command as above but added -r 30 and now file reports:

foo.avi:  RIFF (little-endian) data, AVI320 230 30.00 fps, video: FFMpeg MPEG-4, audio: MPEG-1 Layer 3 (mono, 44100 Hz)

And best of all, Windows Media Player plays the file just fine.

Turns out, you can figure out the frame rate using the following ffmpeg command:

ffprobe foo.avi -show_entries stream=time_base -select_streams v -of compact=nk=1:p=0 

And I had the frame rate set to 1/1000, instead of 1/30. Ooops.

Wednesday, April 09, 2014

Think Like a Unix Geek: Using rsync on Windows to Avoid Restoring From The Cloud

Shira picked up a new laptop a week or so ago. Transferring the files from her old computer to the new one was going to be trivial. She uses CrashPlan as a backup solution, so all she needed to do was install the free version of the app on her new laptop, click the restore tab, and Bam! she was off and running.

Alas, it wasn't so simple. First, the restore dragged and dragged. It took 7 days to download about 400 gigs of data. When she had a mere 10 gigs of data left to restore, the process crashed. When she went to re-execute the restore, it started from scratch. Ugh.

Surely there had to be a better way to get files from Laptop A to Laptop B. The download from cloud method is technically effective, but as I learn every time I restore using Carbonite, it's both fragile and painful.

I noodled over the problem and ended up thinking: if this was a Linux box I'd just kick off rsync and the problem would practically solve itself. Wait a second, I thought, why don't I do just that?

I was inspired by the instructions here. First off, I downloaded rsync through Cygwin on both machines.

On the source (old) laptop, I setup the following /etc/rsyncd.secrets file:

agent:somepass

Also on the source (old) laptop, I setup the following /etc/rsyncd.conf file:

hosts allow = 192.168.1.12 192.168.1.11 192.168.1.14
auth users = agent
secrets file = /etc/rsyncd.secrets
read only = true
use chroot = no
transfer logging = true
log file = /var/log/rsyncd.log

[agent]
path = /cygdrive/c/Users/ShirasUserDirectory

Note: you'll want to tweak the IP's above so they correspond to your network. And you'll want to update the path under the [agent] block. The username 'agent' is fine to use, it doesn't need to exist as a local account or anything.

With those files in place, I was able to run:

 rsync --daemon

 tail -f /var/log/rsyncd.log

and to my shock and amazement, it worked!

From the destination (new) laptop I executed these commands:

  $ cd foo
  $ /usr/local/bin/rsync.exe -av  --ignore-existing   agent@192.168.1.17::'agent/foo/'  

This assumes, of course, that the source laptop is hanging out at 192.168.1.17. The above command asked me for a password, I entered somepass and contents of foo were successfully copied over to my new laptop. The --ignore-existing insures that files that are already on the new laptop don't get re-transferred.

I thought I was home free at this point. I tapped out the following command, hit enter and waited:

  $ /usr/local/bin/rsync.exe -av  --ignore-existing   agent@192.168.1.17::'agent/'  

This however, transferred a few files and then appeared to hang. I came back a few hours later and it had made some progress. But still, it appeared to be stuck.

My first thought was that I ran into the rsync hangs on cygwin problem. There's a number of solutions to this, which I tried.

The thing is, I don't actually think rsync was hanging. I think instead it was just slowing moving data around. When I finally added enough 'v's to the command (-avvvv is more verbose than -avv) I confirmed that data was being transferred.

The next step was to try to narrow down what was being copied. I had some initial success with this command (inspired by this post):

/usr/local/bin/rsync.exe -av --timeout=10 \
  --ignore-existing --exclude='AppData/*' --include='*/'  \
  --include='*.jpg' --exclude='*' agent@192.168.1.12::'agent/' .

This says to exclude the AppData directory (which is filled with temp stuff), include all other folders, include JPEG's and most importantly exclude everything else. This caused rsync to start transferring actually useful jpeg's over to the new computer (versus temp stuff). Finally, some progress. Unfortunately, it was still ridiculously slow.

I looked at what I could optimize next: how about getting rid of the Wireless connection? I dragged both computers downstairs to the physical router and pugged them in. I kicked off the above rsync command. Whooooo! The files whizzed by. I checked the Windows Network Performance, I had gone from 2mb/s to 50mb/s! Now we're talking.

After further experimentation, I kicked off my original command to copy all the files. Finally, the system had enough bandwidth, and it zipped along. At one point, there was a throughput of 300mb/s. Behold, the power of wires!

In the end, I learned some valuable lessons from this experience: (1) never underestimate the power of physical cables, (2) it pays to think like a Unix geek and (3) rsync rocks.

Next restore I'm skipping the download from the cloud and going right to this rsync solution. However, clumsy it is, it's way better than crossing my fingers for a week straight and hoping the massive restore works.