Here's my five latest blog posts - or you can browse a complete archive of all my posts since 2008.

Getting Into the Software Industry

I got this LinkedIn message the other day:

Hi Dylan,

I am a recent high school graduate and I learnt and developed a love and passion for programming through all of your conferences at ndc, art of code and plain text were definitely my favourites.

I’m wondering how I can try to get into the software industry as I’m given to understand it is quite saturated. Most of my experience in recent times is in low level development but I love all forms of software. Any advice is greatly appreciated

and, well, I figure there’s maybe a few other people out there who might appreciate my advice on this, so here goes.

Caveats: I’m an able-bodied English-speaking cishet white guy, which means I get to play life on the easiest difficulty setting. It’s also coming up on three decades since I was a high school graduate, and the last time I had a job interview, one of the questions was about WAP phones.

“Getting into tech” boils down to exactly three things:

  1. You have something to offer
  2. Somebody needs that thing
  3. That person knows who you are and what you can do

First, you have something to offer.

What can you do? What problems do you know how to solve? People often ask me how to get into programming, or how to “learn to code”… I say build something. Finish it, then build something else. Create a website for your friend’s band. Build Tetris. Write a ray-tracer. Write an adventure game. Write a Sudoku solver. Download all the data from IMDB and build a tool that’ll play “Six Degrees of Kevin Bacon”.

Like Jeff Atwood once said: do it in public. Get used to the idea of putting stuff out there which isn’t perfect. Software is never finished, so learn how to tell when it’s good enough to release, and when it’s good enough to put to one side for a while and pick up the next thing.

You also need to realise that it never ends. When I first started programming, when I was about eight years old, I spent hours every week learning how to make the computer do stuff I’d never done before. Nearly forty years later, I still spend hours every single week learning how to make the computer do stuff I’ve never done before. There’s no point where you get to stop and go “that’s it, I’m done. I know programming now. I can stop learning.” Technology is a vast, constantly evolving landscape of problems and solutions, tools, patterns, ideas: some of those turn out to be timeless, some of them will be gone by the weekend.

Second: somebody needs that thing

You want to do this for money? Look at who has money, and what problems they’ve got.

"Getting Into the Software Industry" was posted by Dylan Beattie on 23 July 2024 • permalink

Running Corel Linux on QEMU

In my latest YouTube video, I talk about Corel Linux, which I think is one of the most interesting “what if?” scenarios in the history of mainstream computing, and show some clips of Corel Linux actually running. It’s not easy getting a 25-year-old Linux distro to boot on a modern PC - but here’s how I did it.

Install QEMU

VirtualBox, HyperV et al won’t work here - Corel Linux is just too old to support most of the virtual hardware they expose to their guest operating systems. Instead, we’re going to use QEMU.

Download it from https://www.qemu.org/, install it, get it running. Check it works by running qemu-system-i386 at a terminal window:

> qemu-system-i386 --version
QEMU emulator version 9.0.0 (v9.0.0-12054-g923cf646f4)
Copyright (c) 2003-2024 Fabrice Bellard and the QEMU Project developers
> 

Create a virtual disk

This will create a 2Gb virtual disk file called corel_linux_hd.img:

qemu-img create corel_linux_hd.img 2G

Download Corel Linux

I used the ISO image of Corel Linux 1.2 deluxe from https://archive.org/details/corel_linux_1.2

While you’re there, you might also want to download CorelDRAW and WordPerfect Office https://archive.org/details/CorelForLinux/CorelDRAW%209%20Linux/

Run the installer

qemu-system-i386 -hda corel_linux_hd.img -cdrom corel_linux_1.2.iso -m 256 -vga cirrus

You should get this:

image-20240524131917442

and then this…

image-20240524131927733

and then this:

image-20240524132009470

Yeah. This is where it gets interesting: the Cirrus VGA driver provided by QEMU displays garbled text until we get into the OS and hack it, but we can’t do that until we’ve got it installed, so we’ll need to walk through the installation without being able to read anything.

You probably also don’t have any mouse support, so use Tab to move forwards, Ctrl-Tab to move backwards, and the Spacebar to click:

image-20240524132852073

Don’t change anything here - “Install Standard Desktop” is already selected so just press Enter:

image-20240524132431754

The next few screens, accept all the defaults - tab to Next> if it’s not already focused, press Enter:

image-20240524132552504

image-20240524132659002

and finally, Install:

image-20240524132725616

If you get to here, you’re on the right track:

image-20240524133012196

Once it’s installed, let it reboot, then at the loading screen select Linux - Text Mode

image-20240524133627515

It’ll happily chunter away for a few screens worth of messages, and then you’ll get a login prompt.

Log in a root with a blank password. (Don’t ask. It was the early 2000s. Things were different then.)

Now we need to configure X11 so that it won’t try to use various accelerated hardware features that don’t work in QEMU.

This bit comes from Ethan Gates’ Corel Linux in QEMU post on forums.eeasi.cloud – thank you Ethan! – and that post was in turn inspired by Hayden Barnes’ post The one in which I kind of get Corel Linux 1.2 to work 21 years later - thank you Hayden!

Use vim to edit /etc/X11/XF86config. Find the section called “Device”, add three lines:

Option "no_bitblt"
Option "noaccel"
Option "sw_cursor"

image-20240524140407311

Save the file, reboot (shutdown -r now), and select Corel Linux from the boot menu:

image-20240524140634742

Networking support

Networking support in QEMU is powerful, flexible, and incredibly complicated.

Here’s how I made it work. Disclaimer: I don’t 100% understand exactly what all this does… I tried just about every combination I could think of until I found something that worked.

First, you’ll need to install a TAP network driver. This adds another network interface to Windows, which emulates a physical network card. Apparently.

I installed mine using chocolatety:

choco install tapwindows

Then, in Windows network settings:

  1. Rename the new TAP connection to TapWindows
  2. Right-click your main network connection (ethernet, wifi - whatever connects your Windows machine to the internet)
  3. Properties > Sharing > Allow other network users to connect…
  4. Choose your TapWindows connection as the “Home networking connection”

You’ll get a popup about your LAN adapter. The wording here is misleading - it’s not your LAN adapter, it’s whichever adapter you selected as you” Home networking connection”:

If you remember using Internet Connection Sharing with a dial-up modem… this is the same tech, only we’re pretending that QEMU is our house network, TapWindows is the network card connecting our PC to the rest of the house, and “Ethernet” is the dial-up modem that connects to the internet.

image-20240524141328089

Now, run QEMU using this command line:

I’m using Powershell so a backtick ` is a line continuation character.

qemu-system-i386 -hda corel_linux_hd.img `
-m 256 -vga cirrus -audiodev driver=dsound,id=pa1 -device sb16,audiodev=pa1 `
-netdev tap,id=mynet0,ifname=TapWindows `
-device pcnet,netdev=mynet0

That’s telling QEMU “create a network connection using TAP, call it mynet0, and connect it to the TapWindows interface on the host PC”, and then on the next line “then create a virtual device using the pcnet and connect it to the mynet0 network”.

For me, this worked 80% of the time. When it didn’t work, it’s because something meant the Linux guest couldn’t get an IP address from Internet Connection Sharing’s DHCP server. Manually setting the guest IP to 192.168.137.2, subnet 255.255.255.0, gateway 192.168.137.1 usually fixed this.

That got me to the point where I could boot Linux, open Netscape, point it at http://info.cern.ch/, and browse the world’s first website.

image-20240524142321136

Stuff I Couldn’t Figure Out

I couldn’t get audio working. QEMU audio works fine - I created a Windows 2000 guest just to test this - and I tried both the ac97 and sb16 virtual audio devices in QEMU, but no luck yet.

I also couldn’t figure out how to change the CD-ROM. When you’re running QEMU, Ctrl-Alt-F2 drops you into an emulation console where you can use info block to see which devices/images are connected, and change ide1-cd0 <filename.iso> to change the virtual disk:

image-20240524142715085

But… then I couldn’t work out how to get Corel Linux to mount the disk image, so to install WordPerfect, CorelDRAW!, etc. I had to shut down the VM and then boot it specifying the -cdrom command line switch:

qemu-system-i386 -hda corel_linux_hd.img `
-m 256 -vga cirrus -audiodev driver=dsound,id=pa1 -device ac97,audiodev=pa1 `
-netdev tap,id=mynet0,ifname=TapWindows `
-device pcnet,netdev=mynet0 `
-cdrom .\wordperfect_office_2000_deluxe_cd_1.iso

Ah, the nostalgia.

"Running Corel Linux on QEMU" was posted by Dylan Beattie on 27 May 2024 • permalink

Using the Contour ShuttleXpress with Camtasia

The Contour ShuttleXpress is a controller designed for editing audio and video. I use it all the time in applications like Logic Pro X; the jog dial and shuttle wheel are brilliant for, well, jogging and shuttling around your project’s timeline.

contour-shuttlexpress-camtasia-settings

Camtasia is a screen recording and video editing app from the folks at TechSmith, which I use for doing the screen recordings I use in my presentations and workshops.

Out of the box, the ShuttleXpress doesn’t support Camtasia, so I created an application profile for it.

Action Keystroke What it does
Jog Left , Step backwards in timeline
Jog Right . Step forwards in timeline
Shuttle left/right , . Move forwards backwards in timeline. Each position on the shuttle is independently programmable, so “shuttle in left 7” sends , as fast as possible, “shuttle in left 6” sends 60 strokes per second, and so on. It’s not a perfect timeline shuttle but it’s close enough.
Button 1 Ctrl + Home Jump to beginning of timeline
Button 2 Ctrl+Alt+, Move playhead to previous clip
Button 3 Shift+Ctrl+Alt+Left Arrow Extend selection to previous clip
Button 4 Ctrl+Alt+. Move playhead to next clip
Button 5 Shift+Ctrl+S Split all tracks at playhead

The workflow here is:

  1. Shuttle until you find the start of the mistake
  2. Button 5 to split all tracks
  3. Shuttle forward to the end of the mistake
  4. Button 5 again to split all tracks
  5. Button 3 to select the new region with the mistake in it
  6. Delete to remove the region.

and if you switch on “magnet mode” for the track you’re editing, when you delete the mistake it’ll automatically snap the remaining pieces together so it doesn’t leave a gap.

You can download the settings file here:

CamtasiaStudio-ShuttleXpress.pref

and import it using Settings Management > Options > Import Settings in the Contour Shuttle Device Configuration app:

image-20240317193728441

Happy shuttling :)

"Using the Contour ShuttleXpress with Camtasia" was posted by Dylan Beattie on 17 March 2024 • permalink

Recording Meetups

For the last few months, I’ve been recording the talks at the London .NET User Group on video so we can post them on YouTube after the event. The videos are online at https://www.youtube.com/@LondonDotNet, and I’ve had some questions about the setup I use to record them. Some from interested people looking to do something similar at their own events, some from people who show up early while I’m still setting up and go “wow, what’s with all the shiny things?” So here’s how it works.

dylanbeattie-recording-meetups

(I made a diagram! Want it as a PDF? Here you go: dylanbeattie-recording-meetups.pdf.)

Short answer:

  • Canon EOS M200 camera
  • Elgato Camlink
  • Elgato Game Capture HD60S+
  • AnkerWork 650 wireless microphones
  • OBS Studio on an M1 Macbook Pro
  • Lots of HDMI and USB cables

For the long answer: the key here is minimal post-production. When it comes to editing two hours of video footage, “tomorrow” turns into “later” turns into “never” with astonishing regularity. Other than editing out any major technical problems, I want to leave the meetup with a video file that’s ready to upload to YouTube.

I also don’t want presenters to have to do any more than plug in HDMI, clip on a microphone, and go. I’ve done a few events where I’ve had to install drivers or sharing software for their “wireless smart display” system: I really don’t like doing that, and I’m not going to ask our presenters to do it either.

In terms of portability, I assume the venue will provide HDMI for the presenter’s slides, and there’s a power socket available to plug everything in. Everything else needs to fit in a backpack and set up in less than fifteen minutes.

The camera I use is a Canon EOS M200. It’s a mirrorless SLR with clean HDMI out: what this means in practice is you can plug it into an Elgato Camlink, plug that into your laptop, and it shows up as a webcam - albeit a really, really good one. The camera runs off a USB power supply so I don’t have to worry about running out of battery halfway through a recording.

Bad news: Canon has apparently discontinued the M200, along with the rest of the M series, in favour of the Canon EOS R series. This makes me sad, because the M200 is a wonderful camera.

Good news: Elgato maintain a list of supported cameras, so any of the cameras on this list will do the trick:

https://www.elgato.com/uk/en/s/cam-link-camera-check

If I can, I’ll bring a proper camera tripod along. If not, I’ll bring a SmallRig “magic arm” clamp and find a convenient chair, lamp, window frame or something to clamp it to.

Slides: Elgato Game Capture HD60S+

To capture the speaker’s laptop, I use an Elgato Game Capture HD60S+ (now superseded by the Game Capture HD60 X) This works like a Camlink - turns HDMI into a USB video signal - but it has an HDMI output that uses passthrough, so I can plug it in between the speaker’s laptop and the venue’s projector/screen and then tap a USB feed off the side into my laptop.

Audio: Ankerwork M650 Wireless Microphones

The first time I tried this, I used a RØDE shotgun microphone attached to the camera, but the audio quality wasn’t great, so I invested in a set of AnkerWork M650 wireless microphones. These are wonderful and amazing and excellent and I can’t say enough good things about them, but I’m a big fan of Anker gear, and folks I know who use the RØDE Wireless ME or the DJI MIC system say the same things about their setup.

img

Recording: Macbook Pro + OBS Studio

I run all these devices into an M1 Macbook Pro, and then record everything using OBS Studio.

Set OBS to record MKV format, and enable “automatically remux to MP4” in Settings > Advanced.

If you record direct to MP4, a system crash will leave you with an unusable video file, but MKV files aren’t as widely supported, so recording to MKV and then automatically remuxing to MP4 gives you the best of both worlds.

2024-02-20_11-39-52 (1)

This is where the “minimal post-production” bit kicks in. I’ve got four scenes set up in OBS:

Camera Only:

Slides + Camera:

2024-02-20_11-09-02

Camera + Slides:

2024-02-20_11-09-11

Slides Only:

2024-02-20_11-09-18

Plus, before the meetup I’ll make a title slide for each speaker and create a scene with that title slide plus the microphone, which means you can record audio over the title - great for introductions and preamble:

2024-02-20_11-09-22

Note that “Slides + Camera” has the slides on the left, and “Camera + Slides” is the same thing, but with the slides on the right. Composition works better if the speaker is facing towards the middle of the frame, so which one of these gets used depends on where the camera is in relation to the speaker on the night.

OK, showtime. Plug everything in, get it working, probably reboot a couple of things because HDMI can be tricksy.

Mic up your presenter, and check the audio in every scene - nothing worse than recording the whole event and finding out later that one of your presents wasn’t recording any sound. Ask your speaker to stand where they’re going to stand, check the camera angle. I clip the other mic on to myself so I can introduce the speaker, repeat questions, etc. and have it come through on the recording.

Then bring up the title slide, hit Record, introduce the talk, and then switch scenes as you go, and all being well, you’ll end up with a MP4 file you can upload straight to YouTube when you get home.

Shopping List:

Cables:

5 metres seems to be the sweet spot for cable length. Too short, and you’re going to be in the way. Too long and you get weird signal problems, or no signal at all.

  • MicroHDMI > HDMI cable (camera HDMI OUT > Camlink HDMI)
  • Regular HDMI cable (presenter laptop > GameCapture IN)
  • USB-C cable (GameCapture OUT > OBS)

Total investment if you’re starting from scratch will come to just over £1200.

How much?!

OK, you don’t need to spend that all in one go. Get a copy of Reincubate Camo and use your phone instead of buying an SLR camera + Camlink. For slides, you can pick up cheap HDMI splitter and an HDMI > USB capture gizmo on Amazon for about £20 each, and see how you go.

I wouldn’t skimp on microphones, though: as they say in Hollywood, “your ears never blink”, and you can probably get away with significantly worse video quality if the audio is up to scratch.

Here’s two clips I recorded using the old camera-mounted RØDE shotgun mic:

and here’s some recorded with the AnkerWork mics:

So, that’s your lot. Good luck with it, and let me know if you found this useful.

"Recording Meetups" was posted by Dylan Beattie on 20 February 2024 • permalink

Custom Validation Attributes in ASP.NET Core 8

ASP.NET Core 8 rocks. It’s fast, powerful, cross-platform… and, yes, includes jQuery.

Chill. jQuery’s fine. It’s solid, it’s proven, it works, and we’re not here to impress hipsters hiring frontend devs for their Web3 startup, we’re here to build stuff that works (and will probably still work next year.)

Out of the box, ASP.NET Core includes jQuery, jquery.validate and jquery.validation.unobtrusive, which together provide client-side validation that integrates really closely with the server-side validation provided by ASP.NET and System.ComponentModel.

Most of the stuff you’ll find online about how to extend ASP.NET Core validation still works, but there’s a few new things in .NET 8 which make it a little cleaner. Specifically, IDictionary<TKey,TValue> in .NET 8 includes a .TryAdd(key, value) method, which means the MergeAttributes helper method you’ll find in lots of examples of custom validation attributes isn’t required any more.

Here’s how to extend ASP.NET with a custom validation attribute that’ll make a checkbox a required field – the classic “You must accept the terms and conditions” scenario.

The attribute itself:

public class MustBeTrueAttribute : ValidationAttribute, IClientModelValidator {

	public override bool IsValid(object? value) => value is true;

	public void AddValidation(ClientModelValidationContext context) {
		var errorMessage = FormatErrorMessage(context.ModelMetadata.GetDisplayName());
		context.Attributes.TryAdd("data-val", "true");
		context.Attributes.TryAdd("data-val-must-be-true", errorMessage);
	}
}

You’ll need to add two lines of custom JavaScript to your pages - I add these to the end of _ValidationScriptsPartial.cshtml.

<script>
	jQuery.validator.addMethod("must-be-true", (_, element) => element.checked);
	jQuery.validator.unobtrusive.adapters.addBool("must-be-true");
</script>

Don’t put this code inside the jQuery onload handler (the one that’s normally wrapped in $(function() { }) – it doesn’t rely on any DOM elements and the call to jquery.validator.unobtrusive.adapters.addBool() has to run before the validation code parses your form.

To use this attribute in your view model:

public class SignupPostData {
	[MustBeTrue(ErrorMessage = "You must accept the terms and conditions")]
	public bool AcceptTerms { get; set; }
}

and then in your Razor view:

@model SignupPostData

<form method="post">
    <label asp-for="@Model.AcceptTerms">
        <input type="checkbox" asp-for="@Model.AcceptTerms"/>
        I accept the terms and conditions
    </label>
    <span class="form-text text-danger" asp-validation-for="@Model.AcceptTerms"></span>
</form>

@section Scripts {
	@{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); }
}

or, if you prefer using the HTML Helper syntax:

@model SignupPostData

@using (Html.BeginForm(FormMethod.Post)) {
	@Html.ValidationSummary()
	@Html.LabelFor(model => model.AgreeToPayment)
	@Html.CheckBoxFor(model => model.AgreeToPayment)
	@Html.ValidationMessageFor(model => model.AgreeToPayment)
	<input type="submit"/>
}

@section Scripts {
	@{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); }
}
"Custom Validation Attributes in ASP.NET Core 8" was posted by Dylan Beattie on 24 January 2024 • permalink