OS X 10.11 El Capitan and Jenkins CI install by Brian Lambert

I'm setting up BVT / CI for an Objective-C project for iOS and I picked Jenkins CI as my build automation server.

The good news is that Jenkins CI looks amazing.

The bad news is that the Mac OS X native package didn't work well on OS X El Capitan 10.11.3.

Here's what happened.

I installed the Mac OS X native package. When I did, it completed and launched http://localhost:8080/ in my default browser. Instead of seeing Jenkins, though, I saw:

The first thing I tried was to uninstall:

/Library/Application\ Support/Jenkins/Uninstall.command

And re-install, but this didn't work. After some poking around, I found that my clean OS X 10.11 El Capitan doesn't come with Java and that the Jenkins installer simply doesn't check for this vital prerequisite.

So, install the JDK from Oracle (this link worked for me: http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html)

And you'll be all set.

Next, follow the instructions here: http://www.cimgf.com/2015/05/26/setting-up-jenkins-ci-on-a-mac-2/

Bubble Chat and TSNPeerBluetooth Cocoapod... by Brian Lambert

As part of the Developer Experiences team at Microsoft, I've been contributing to an open source project we're working on called Thali. (Visit thaliproject.org for more information on Thali).

Part of my work on Thali has been to investigate what is possible for peer-to-peer networking on iOS using Apple's Core Bluetooth and Multipeer Connectivity frameworks.

Inspired by apps like FireChat, I wanted to see how difficult it would be to develop a chat app for iOS that worked entirely over Bluetooth LE.

I called this app Bubble Chat. Here's a video showing Bubble Chat in action:

How Bubble Chat Works

Conceptually, there is one, worldwide bubble in Bubble Chat. Each user running Bubble Chat has a different view of this bubble that varies and is based solely on the set of nearby peers it can communicate with on Bluetooth LE over time. So, when another user running Bubble Chat enters your Bluetooth LE communications range, they enter your view of the bubble and you enter their view of the bubble, and the two of you can communicate with each other for as long as you remain within range. When two peers are out of Bluetooth LE communications range, they can no longer communicate with one another and their views of the bubble will diverge accordingly.

It's kind of neat concept, and it's also sort of pointless. :-) But that's OK. I only built Bubble Chat to be an example of how one might use Apple's Core Bluetooth framework for peer-to-peer networking.

The Code

Here's a link to the Bubble Chat repo on Guthub.

Bubble Chat makes use of a Cocoapod I wrote called TSNPeerBluetooth. Here's a link to the TSNPeerBluetooth repo on Github.

I should note that TSNPeerBluetooth isn't designed to be a generally useful Cocoapod for other applications. It was built solely for Bubble Chat and to serve as a useful, standalone iOS example of how to define a custom Bluetooth LE service and set of characteristics, act as a peripheral for that service, and as a central that consumes that service.

Development Notes

On iOS, an app can act as both a Bluetooth LE accessory (it can be a Bluetooth LE peripheral, in Core Bluetooth parlance) and it can use Bluetooth LE accessories (it can be a Bluetooth LE central, in Core Bluetooth parlance).

When an app is in the foreground, all of this works as you would expect. A foreground app is allowed to use Bluetooth LE as it sees fit to meet its needs.

When an app is in the background, however, things work quite differently.

Apple works very, very hard to keep iOS apps that are in the background from consuming resources (using the radios, using the GPS receiver, running code, etc.) so that battery usage for background apps, and thus the device itself, remains very low.

By default, when an iOS application is in the background it can't do anything. (The exception being that one can use a background task to complete work that was being done when the app was put into the background, but only for a short, unspecified period of time.) 

Apple provides a way for an app to do certain things in the background by declaring what it needs to do in Capabilities / Background Modes:

The Bubble Chat application declares three such Background Modes:

  1. Location updates in the background.
  2. Uses Bluetooth LE accessories in the background.
  3. Acts as a Bluetooth LE accessory in the background.

Declaring these Background Modes seems like it would allow an app to do everything in the background that it can do in the foreground with respect to receiving location updates, using Bluetooth LE accessories, and acting as a Bluetooth LE accessory. This isn't the case, though. In particular, when an iOS app is in the background, Bluetooth LE behaves very differently. This is a very long and complicated topic which is covered in some detail in the Core Bluetooth Background Processing for iOS Apps documentation. But this documentation doesn't tell the entire story. In particular, it does not discuss why it is that an iOS app such as Bubble Chat - which acts as both a Bluetooth LE peripheral and central - cannot discover and connect to other instances of itself when all instances are in the background.

  • When an iOS device that's acting as a peripheral goes into the background, it changes its advertisement packets to not include service UUIDs.
  • When an iOS device that's acting as a central goes into the background, it only scans for specific service UUIDs.

These two background behaviors combine to prevent background-to-background iOS advertisement / discovery.

There are numerous postings about this topic on Stack Overflow and other sites which erroneously claim that background-to-background iOS advertisement / discovery is possible. As of iOS 8.3, it is not possible.

I contacted Apple support about this and, after a few iterations, they have acknowledged that it is indeed true and have instructed me to file a bug report / enhancement request for it. I filed enhancement request #20655911 on 23-Apr-2015.

Here's a video which illustrates background-to-background iOS advertisement / discovery:

I'm continuing to work on Bubble Chat and TSNPeerBluetooth, and I welcome your thoughts, ideas, and contributions.

How to make NSButton to use subpixel antialiasing by Brian Lambert

For some reason NSButton doesn't use subpixel antialiasing for its label like other AppKit controls such as NSSegmentedControl.

Here's an example:

I noticed this right away and I found it really annoying. 

In order to fix this in my work, I created a subclass of NSButton which overrides the drawRect method and enables subpixel antialiasing for drawing the label.

Here's the code:

// Draw the receiver’s image within the specified rectangle.
- (void)drawRect:(NSRect)rect
{
    // Get the graphics context.
    CGContextRef context = [[NSGraphicsContext currentContextCGContext];
    
    // Save the graphics context state.
    CGContextSaveGState(context);
    
    // Set up font rasterization so that subpixel antialiasing is used.
    CGContextSetShouldSmoothFonts(context, TRUE);
    CGContextSetShouldSubpixelPositionFonts(context, TRUE);
    CGContextSetShouldSubpixelQuantizeFonts(context, TRUE);
    
    // Draw the button.
    [super drawRect:rect];
    
    // Restore the graphics context state.
    CGContextRestoreGState(context);
}

The result can be seen below. Both buttons were created using Interface Builder, but the top button has its class set to a subclass with the drawRect override. The bottom button does not. The improvement is easily noticeable:

Curiously, Apple seems to have done something like this for its own applications. I checked out buttons in System Preferences, and their labels are drawn with subpixel antialiasing. I wonder why this isn't the case for apps that people make using Interface Builder? Odd...

Working for Microsoft! by Brian Lambert

I'm super happy to announce that I am now working for Microsoft as a member of the Developer Experience Group (DX), which is part of the Strategic Engagements Team.

In my new role, I'll be working directly with Microsoft's strategic partners and customers to help them use Microsoft products and services, devices, and open-source technologies to build and deliver concrete solutions that change their business.

I couldn't be happier about having this opportunity to re-join Microsoft and to help the company and its customers be more successful.

 

Disable integrated graphics on MacBook Pro by Brian Lambert

Automatic graphics switching is a power saving feature that allows OS X to use the integrated graphics card whenever it determines that high-performance graphics capabilities are not being used.

Who wants that? I paid for the NVIDIA GeForce GT 750M, and I want it on all the time.

Use the checkbox labeled Automatic graphics switching at the top of the Energy Saver panel of System Preferences to disable this "feature" and get the Mac that you paid for back.

While this feature might save a little bit of energy, the subtle display glitches it introduces aren't worth it.

Simple helpers for dispatch_async by Brian Lambert

Below are a couple of simple helpers I wrote that make working with Apple's Grand Central Dispatch easier on the eyes.

The first method is called OnMainThread. It executes a block on the main thread. Additionally, it detects whether it was called on the main thread, and simply executes the block, if it was.

// Executes the specified block on the main thread.
static inline void OnMainThread(dispatch_block_t block)
{
    // If this is the main thread, then just execute the block; otherwise,
    // dispatch the block.
    if ([NSThread isMainThread])
    {
        block();
    }
    else
    {
        dispatch_async(dispatch_get_main_queue(),
                       block);
    }
}

The second method is called OffMainThread. It executes a block off the main thread. Additionally, it detects whether it was called off the main thread, and simply executes the block, if it was.

// Executes the specified block off the main thread.
static inline void OffMainThread(dispatch_block_t block)
{
    // If this isn't the main thread, then execute the block; otherwise,
    // dispatch the block.
    if (![NSThread isMainThread])
    {
        block();
    }
    else
    {
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,
                                                 0),
                       block);
    }
}

Here's an example of how these helpers can be used together. Off the main thread, this example performs a complex calculation and formats the result of the computation for display. Next, on the main thread, it updates the UI.

OffMainThread(^{
    double bigNumber = [self computeBigNumber];
    NSString * bigNumberText = [NSString stringWithFormat:@"%.2f", bigNumber];
    
    OnMainThread(^{
        [[self labelBigNumbersetText:bigNumberText];
    });
});

Ubuntu on Lenovo Yoga 2 Pro is Awesome by Brian Lambert

I've just installed Ubuntu GNOME 13.10 on a Lenovo Yoga 2 Pro with only minor annoyances.

There's a lot of hoopla over on ask ubuntu about this model. Perhaps some of it refers to the initial revisions of the computer? I don't know. Mine was made on Jan 26, 2014 and what I can tell you is that it more or less works flawlessly.

Installation instructions to wipe Windows 8 and install Ubuntu (in my case, Ubuntu GNOME 13.10):

  • Turn the machine off.
  • Insert Ubuntu USB boot memory stick.
  • Start machine while holding Fn+F2. Enter BIOS and move the USB boot stick to #1 position. There's no need to do anything else.
  • Exit & Save. Reboot.
  • If your screen is black, press F12 to brighten it. DUH. (In general, if your screen is black when installing Ubuntu, try to increase the brightness using the keyboard before going off to try more elaborate fixes.)
  • Select Install Ubuntu / Erase disk. Don't worry about being on the Internet. Just install it.
  • Reboot.
  • Once Ubuntu comes up, open a terminal and enter:
sudo rmmod ideapad_laptop
  • This will enable your wireless. Set it up, and then update the system using Software Updater. Once it's all up to date, install your development poop and start coding. 

For now, it appears that on each fresh boot you will have to remove the ideapad_laptop module from the Linux Kernel using the rmmod command above. Big whoop. This is simple enough to do and the problem will most likely get fixed at some point.

Now, the display on this laptop is incredibly high-res. 3200x1800. I found that changing the resolution to 1920x1080 produces an amazingly readable display. Perfect to my eye. One day there will be High DPI support in Linux. For now, this is awesome. Don't think twice about buying this machine because of the reports out there that claim it looks crappy at a lower resolution. They are simply wrong.

Now, the cool stuff. This machine is AWESOME.

The 256 GB SSD in my machine delivers blazing performance:

Benchmark_001.png

Geekbench 3.1.4 for Mac OS X x86 (64-bit) reports:

 3,198 Single-Core
 6,197 Multi-Core

Click here for my test results.

This is impressive. It's within a hair of my MacBook Air and cost about 1/2 as much.

// EOF

Setting up a swap file on the 13-inch MaxBook Air by Brian Lambert

When I built the MaxBook Air, I decided not to go for the more complex swap partition approach that some people espouse. I tried this in my previous installation and I felt that it made performing the partitioning in Mac OS X far more difficult for no real benefit.

Instead, I opted to build and use a swap file.

Here's how I did it.

First, you can check out your current swap situation using the free -m command. After installing Ubuntu GNOME 13.10 on my MaxBook Air, this is what it looked like (edited for clarity):

➜~ free -m
Swap: total: 0 used: 0 free: 0

As can be seen, swap is all zeros. Let's fix that.

First, we need to decide how much swap to use. This will depend on your needs, but generally, swap space equal in size to the physical RAM installed in the machine is a good rule of thumb. Since the MaxBook Air has 8 GB of physical RAM, that's the size of the swap file I will make.

First, allocate space for the swap file (8g is 8 GB).

sudo fallocate -l 8g /swapfile

Next, change the permissions on the swap file so it cannot be read by other users. (For my world this isn't strictly necessary - there are no other users - but it's best practice to do so.)

sudo chmod 600 /swapfile

Next, set-up the Linux swap area in the swap file:

sudo mkswap /swapfile

Next, add the swap file to the running system:

sudo swapon /swapfile

At this point, let's run free -m again to check on what we've done (edited for clarity):

➜~ free -m
Swap: total: 8191 used: 0 free: 8191

So, we now have a swap file!

We can also check on this using the System Monitor.

 8,0 GB Swap

If we stop here, the next time we boot the system it won't know about the swap file. We need to configure the OS to use this swap file. To do this, we must edit the /etc/fstab file as root.

I use Sublime Text so I used the following command:

sudo subl /etc/fstab

*Use your favorite editor here. gedit also works.

And add this line to the bottom of the file:

/swapfile none swap sw 0 0

Reboot your machine and use free -m and / or System Monitor to verify that your edit worked.

// EOF

13-inch MaxBook Air! by Brian Lambert

It turns out that perhaps the best Linux Ultrabook-class laptop is a 13-inch MacBook Air. Who knew? I am calling mine my "MaxBook Air".

Last summer I shelled out a bagillion dollars for a new MacBook Air. It's a 13-inch Mid 2013 model (MacBookAir6,2). I got the top of the line unit; I spared no expense.

  • 1.7GHz dual-core Intel Core i7 (Turbo Boost up to 3.3GHz) Intel Core i7-4650U
  • 8 GB 1600MHz LPDDR3
  • 512GB SSD

I bought it mainly for being able to work in coffee shops for hours on end without AC power, and also because it's so small and light.

Performance

The Geekbench 3 test results for this tiny little laptop are really impressive.

Geekbench 3.1.4 for Mac OS X x86 (64-bit) reports:

 3,265 Single-Core
 6,379 Multi-Core

This is within a few shades awesome. Compare this machine with a new Mac Pro (Late 2013) model costing $3,000.

Geekbench 3.1.4 for Mac OS X x86 (64-bit) reports:

 3,369 Single-Core
21,167 Multi-Core

Obviously with more cores the new Mac Pro wins, but the diminutive MacBook Air is within a few shades of it in the single-core measurement. This tiny little machine is unquestionably a processing powerhouse!

And it's beautiful. Check out the teardown of the unit at iFixit.

What's even more impressive than the processing performance of this little machine is the disk performance. With the 512 GB SSD option, it's a screaming demon. Blackmagic's Disk Speed test results are simply insane. Over 700 MB/s.

DiskSpeedTestAir.png

Linux disk speed tests bear out these numbers, or close to them. 790 MB/s read, 493 MB/s write. Whatever the real numbers are, the SSD in this machine is bloody quick. Quick enough for me.

Setup

Setting up the system could not be easier, once you know the secrets.

Here are the steps.

  • Download Ubuntu 13.10 or Ubuntu GNOME 13.10. I use Ubuntu GNOME 13.10, and I downloaded it here: Download Ubuntu GNOME 13.10
  • Make a bootable USB thumb drive. (I used a 16 GB SanDisk Ultra.)
    • On Linux, use usb-creator. Instructions can be found here.
    • Making a bootable USB thumb drive on OS X is a little more challenging, but very doable. Just follow the instructions here. I've tested them and they work.
  • Once you have a bootable USB thumb drive, partition your OS X system drive to make space for Ubuntu. I simply carved my 512 GB SSD into two partitions, ~250 GB each. I used the "Partition Layout" drop-down to do this. It takes the guess work out of the operation.
pm.png
  • Once your disk is partitioned, shutdown OS X and have the machine in a powered off state.
  • Obtain a USB or Thunderbolt Ethernet adapter and plug it into the MacBook Air and your hardwired Ethernet network.
  • Plug the MacBook Air into its power adapter.
  • Insert the bootable USB thumb drive and power on the machine while holding the option (alt) key.
  • When the boot menu comes up, select the USB thumb drive. It will most likely be labeled EFI Boot. This will boot Ubuntu or Ubuntu GNOME from the bootable USB thumb drive.
  • When prompted select "Install Ubuntu".
    • If you prefer, you can select the "Try Ubuntu" option and follow these instructions using the Install Ubuntu program. You'll find it on the favorites bar. This approach will let you play around with Ubuntu before installing it.
  • Select your language.
Screenshot from 2014-02-26 02_36_58.png
  • On the next screen I typically select everything.
Screenshot from 2014-02-26 02_44_16.png
  • Then on the next screen select "Something else" for the installation type.
Screenshot from 2014-02-26 02_45_36.png
  • At this point you will be presented with advanced options for the installation type.
mScreenshot from 2014-02-26 02_47_28.png
  • Do not modify the partitioning. Only modify partitioning in OS X.
  • Find the new partition (/dev/sda3 most likely) and use Change... to select ext4 as the format and / as the mount point. This tells the installer where to install Ubuntu.
    • Be careful not to select the hfs+ partition as this is where your OS X installation lives and, if you do, you will overwrite your Mac OS X installation.
    • Do not select a swap partition. For now, you have plenty of RAM to run the OS and perform the installation, so just install Ubuntu. My next post will cover adding a swap file.
  • Select /dev/sda3 as the install location and perform the installation by pressing the "Install Now" button.
    • Worth saying again! Be careful not to select the hfs+ partition as this is where your OS X installation lives and, if you do, you will overwrite your Mac OS X installation.
  • Let the install complete and reboot.
  • Remove the bootable USB thumb drive.

At this point, Ubuntu will be installed, but OS X will boot. Don't panic! It's there, but you can't get to it yet. All you need is a tool to get it up and running.

When OS X boots, log in and then download and install rEFIt. Here's the link. Follow the instructions you'll find there.

After rEFIt installs, reboot your machine, then reboot it again. You will then see the rEFIt menu when your system boots. On this menu, select the "EFI\ubuntu\grub.64.efi" entry to boot Ubuntu, or the Apple logo to boot OS X.

 Booting Ubuntu.

Booting Ubuntu.

And that's it. Each time you boot, you'll be able to decide whether you want to run Ubuntu or Mac OS X.

// EOF

My bash prompt by Brian Lambert


I like my bash prompt to be short and, if I'm in a directory with a git repository, I like it to tell me which branch I've got checked out.

Here's how I set up my git-aware bash prompt in .bashrc:

# ANSI color codes
RS="\[\033[0m\]"# reset
HC="\[\033[1m\]"# hicolor
UL="\[\033[4m\]"# underline
INV="\[\033[7m\]" # inverse background and foreground
FBLK="\[\033[30m\]" # foreground black
FRED="\[\033[31m\]" # foreground red
FGRN="\[\033[32m\]" # foreground green
FYEL="\[\033[33m\]" # foreground yellow
FBLE="\[\033[34m\]" # foreground blue
FMAG="\[\033[35m\]" # foreground magenta
FCYN="\[\033[36m\]" # foreground cyan
FWHT="\[\033[37m\]" # foreground white
BBLK="\[\033[40m\]" # background black
BRED="\[\033[41m\]" # background red
BGRN="\[\033[42m\]" # background green
BYEL="\[\033[43m\]" # background yellow
BBLE="\[\033[44m\]" # background blue
BMAG="\[\033[45m\]" # background magenta
BCYN="\[\033[46m\]" # background cyan
BWHT="\[\033[47m\]" # background white

# Set the prompt.
export PS1="[$HC$FBLE\w/$FCYN\$(__git_ps1 '(%s)')$RS]: "
 Here's how it looks. You can change your colors to match your needs by replacing $FBLE and $FCYN with colors you prefer.

Here's how it looks. You can change your colors to match your needs by replacing $FBLE and $FCYN with colors you prefer.

Awesome screen shot utility by Brian Lambert


If you're looking for a way to capture screen shots on Linux, look no further than Shutter.

It's awesome.

 Shutter capturing itself after having captured Disks.

Shutter capturing itself after having captured Disks.

The standard gnome-screenshot program works pretty well, but it has lots of bugs. Shutter works perfectly. It's a "must have" tool.

Here's how I installed Shutter on Unbuntu GNOME:

sudo apt-get -y install shutter

Checkout the Shutter Project website for more information.

#eof

Maximize dev box SSD performance by Brian Lambert

Since my machine is dedicated to development use, I don't care about the last time a file or directory was accessed. Nor do I use any programs that do. So, in order to maximize performance, and keep it that way over time, I adjusted the mount options of my SSD system disk as follows:

  • noatime - Don't record last accessed time for files.
  • nodiratime - Don't record last accessed times for directories.
  • discard - Enable TRIM

Here are the adjusted mount options:

 Updated mount options for my SSD system disk.

Updated mount options for my SSD system disk.

Additionally, I want to store all my code on another disk that is mounted in the ~/Code directory so that I can rebuild my system any time I like without losing all my local git repositories.

To do this, I changed the mount options on my second SSD as follows:

 Updated mount options for my second SSD disk.

Updated mount options for my second SSD disk.

So, with these things done, my machine is set-up for optimal performance and resiliency in the case of something going wrong with my system disk or installation.

#eof

Switching to Ubuntu GNOME from OS X by Brian Lambert

gnome.jpg

I'm switching from OS X to Ubuntu GNOME 13.10 for my Ruby on Rails development. I'm also starting to work on learning GTK programming.

The Office

At the office, I'm using an ASUS M51AD that I picked up from Best Buy. I didn't want to bother with a custom build for the office, so this machine is good enough.

To this machine I've added a Samsung 250 GB 840 EVO. With > 500 MB/s RW, it's as fast as I need in a disk for RoR development.

Geekbench 3.1.4 for Linux x86 (64-bit) reports:

 4,006 Single-Core
15,034 Multi-Core

Click here for the report.

This machine is mated to a Dell U3014 UltraSharp 30-Inch PremierColor Monitor and another nondescript DELL 1900x1200 monitor as a second screen. It's a pretty sweet set-up.

At Home

At home, I went for a custom build.

I started off with an EVGA Hadron Air mini ITX case. It was bold, but I decided that I wanted to go mini ITX so I would have a compact system.

For a motherboard I went with an ASUS Z87I-DELUXE. It has Elite status from Tom's Hardware. Here's the review I read which helped me pick it.

The processor is an i7-4770K.

Memory is 16 GB of Crucial Ballistix - nothing too special about that.

The boot drive is a Toshiba 128 GB HDTS312XZSTA. It's also capable of > 500 MB/s RW, so plenty fast and large enough for the OS.

The development disk is another Samsung 250 GB 840 EVO. That's where I keep all my git repositories.

I've overclocked the rig to 4.2 GHz.

Geekbench 3.1.4 for Linux x86 (64-bit) reports:

 4,351 Single-Core
16,620 Multi-Core

Click here for the report.

This machine is mated to a HP ZR2740w 27-inch LED Backlit IPS Monitor. This is essentially the exact same screen as an iMac 27" display. I also have a DELL 24" 1920x1080 IPS display as a second screen.

And Mac Pro...

Now, here's the interesting part... I built the home machine for well under $1,500, including the sweet HP monitor (which I got on sale at Micro Center).

Compare this machine with a new Mac Pro (Late 2013) model costing $3,000 (which, by the way, only has 12 GB of RAM):

Geekbench 3.1.4 for Mac OS X x86 (64-bit) reports:

 3,369 Single-Core
21,167 Multi-Core

Click here for the report.

Clearly, the Mac Pro is not a good value in terms of price/performance. I've been a big-time Mac fanboy for several years now, but lately... Not so much. I certainly love the industrial design of MacBook  Pro and Air laptops, but when it comes to the industrial design of my desktop machine, I could care less.

That's all for now.

Brian

Syntax Formatter for Xcode by Brian Lambert

Some time ago I wrote a nice little utility for people who blog about iOS and OS X development called Syntax Formatter.  Well, I'm happy to say it's still there, it still works, and it allows one to post very nicely-formatted code snippets from Xcode to a blog.

Here's an example:

// Class initializer.
- (id)initWithFrame:(CGRect)frame
{
    // Initialize superclass.
    self = [super initWithFrame:frame];
    
    // Handle errors.
    if (!self)
    {
        return nil;
    }
    
    // Initialize the view.
    [self setOpaque:NO];
    [self setClipsToBounds:YES];
    [self setBackgroundColor:[UIColor blackColor]];
    
    // Allocate, initialize, and add the home view.
    _homeView = [[HomeView allocinitWithFrame:[AppContext screenRect]];
    [_homeView setDelegate:(id <HomeViewDelegate>)self];
    [self addSubview:_homeView];
    
    // Done.
    return self;
}

As you can see, it looks just like Xcode's Default Fonts & Colors.

It's free! Have fun!