Upgrades and such

I spent a few hours this weekend making improvements to kiniro dot uk:

  • We're now running on the very latest versions of both Ruby and Rails.
  • Posts on the front page are now paginated, with 5 shown per-page.
  • Every page now includes Open Graph Protocol meta tags, so articles can generate a nice "preview" when linked to in Slack et al.

There's still a bevy of behind-the-scenes improvements to be made, which I'll look into when I have more free time.

I made a Firefox add-on (and so can you)

This morning, I came across someone complaining that the option to use the old Youtube layout has been removed. A quick search turned up this Reddit post:

Attaching "disable_polymer=1" to the arguments in the URL will keep the page using the previous layout. Unfortunately though that setting doesn't carry over to other pages when you load a new one.

Automatically appending an extra parameter to the end of Youtube URLs didn't sound too difficult, so I decided to use it as an opportunity to learn about building browser add-ons... and it turns out that it really is pretty simple:

  • I started off by looking at "Your First WebExtension" on MDN. This example is based around injecting scripts into pages, though, which isn't quite what I wanted to do.
  • A bit of searching led me to "Intercept HTTP Requests"... which is more-or-less exactly what I wanted to do!
  • After a few misadventures, I put everything in a .ZIP file (as per "Package Your Extension") and called it a day.

I've now uploaded it to the Firefox add-ons site, and have made the source code available on Gitlab.com.


PROTIP: Don't forget that your intercept-if-youtube.com logic will also catch any redirects to youtube.com... like, say, the ones with &disable_polymer=1 on the end that your code generates. Oops!

Kiniro updates

I finally updated Kiniro to use the actual, final version of Rails 5.2.0 last weekend, having left it running on 5.2.0.rc1 for a while. I also fixed various strange RSpec/MySQL connection problems, and made everything Rubocop-compliant, which - for some reason - I apparently didn't do before.

This weekend, I've attempted to make things look nicer by updating the CSS. I also made a small improvement to the post editor: It now delays the re-draw of the Markdown "live preview" area for about one second, so that continuous typing doesn't cause constant re-parsing/drawing:

var update = function () {
  // ...
};

// Update after a short delay if no further change events are fired:
var timeout = null;
var queueUpdate = function () {
  clearTimeout(timeout);
  timeout = setTimeout(update, 1500);
}

update();
content.addEventListener('change', queueUpdate);
content.addEventListener('keyup', queueUpdate);

(This used to be a bit of a problem with longer posts - especially if they had embedded images or Youtube videos!)

There are still a few things to do, but most of them are behind-the-scenes stuff, like improving the CSS for pages only logged-in users can see.

OpenMW

I've been thinking about Morrowind again recently, so a few days ago I tried installing OpenMW - a cross-platform, open-source reimplementation of the Gamebryo engine it runs on.

Installation was easy; OpenMW itself is available from the Debian package repository, so after downloading/installing the necessary .deb files I simply ran the launcher program, which copied everything from the retail CD to my hard drive before running the game.

(I did run into one minor hiccup - a message box with "ERROR: Unknown fallback name: FontColor_color_header" when starting the game for the first time - but that was easy enough to fix, thanks to the OpenMW wiki.)

I've only just started playing with a new character, so I've barely made it out of Seyda Neen at the moment, but from what I've seen so far it works flawlessly - even on my ancient 2008-era desktop machine!

If you're considering (re-)playing Morrowind, especially on a not-Windows OS, you might want to give it a whirl.

1337 preprocessor trix

After a long hiatus, I've recently decided to look at the Flash file format again. I may write more about it at some point - there's certainly plenty of interesting quirks to discuss - but today I'd like to share something I learned about C preprocessor macros.

† At time of writing, my last commit on the Vector2D project was just over three years ago.


One of the simplest uses for #define is to give a name to a common value:

#define SWF_TAG_SET_BACKGROUND_COLOR 9

You can also use it to create function-like substitutions like so:

#define swfDecodeTwips(value) ((value) / 20.0)

However, the search/replace logic only matches exact text:

#define single(x) One x
single(apple)
/* Output for `gcc -E example.c` is "One apple" */

#define plural(x) Two xs
plural(apple)
/* Output for `gcc -E example.c` is "Two xs" */

To get around this, you can use the ## operator:

#define plural(x) Two x##s
plural(apple)
/* Output for `gcc -E example.c` is "Two apples" */

With a bit of cleverness, this can be used to define "template" functions for cases where near-identical logic applies to distinct structures - something that other languages might support via inheritance, generics, or mixins:

#define swfDefineCreateFunction(PARENT, CHILD, NAME)\
  Swf##CHILD *swfCreate##CHILD(Swf##PARENT *p) {\
    void *tmp;\
    if (!p) return NULL;\
    tmp = realloc(p->NAME, (p->num_##NAME##s + 1) * sizeof(Swf##CHILD*));\
    if (!tmp) return NULL;\
    p->NAME = (Swf##CHILD**)tmp;\
    p->NAME[p->num_##NAME##s] = (Swf##CHILD*)malloc(sizeof(Swf##CHILD));\
    if (!p->NAME[p->num_##NAME##s]) return NULL;\
    memset(p->NAME[p->num_##NAME##s], 0, sizeof(Swf##CHILD));\
    ++p->num_##NAME##s;\
    return p->NAME[p->num_##NAME##s - 1];\
  }

static swfDefineCreateFunction(Shape, Path, path)

/*
static SwfPath *swfCreatePath(SwfShape *p) {
  void *tmp;
  if (!p) return NULL;
  tmp = realloc(p->path, (p->num_paths + 1) * sizeof(SwfPath*));
  if (!tmp) return NULL;
  p->path = (SwfPath**)tmp;
  p->path[p->num_paths] = (SwfPath*)malloc(sizeof(SwfPath)); 
  if (!p->path[p->num_paths]) return NULL;
  memset(p->path[p->num_paths], 0, sizeof(SwfPath));
  ++p->num_paths;
  return p->path[p->num_paths - 1];
}
*/

swfDefineCreateFunction(Movie, Shape, shape)

/*
SwfShape *swfCreateShape(SwfMovie *p) {
  void *tmp; 
  if (!p) return NULL; 
  tmp = realloc(p->shape, (p->num_shapes + 1) * sizeof(SwfShape*)); 
  if (!tmp) return NULL; 
  p->shape = (SwfShape**)tmp; 
  p->shape[p->num_shapes] = (SwfShape*)malloc(sizeof(SwfShape)); 
  if (!p->shape[p->num_shapes]) return NULL; 
  memset(p->shape[p->num_shapes], 0, sizeof(SwfShape)); 
  ++p->num_shapes; 
  return p->shape[p->num_shapes - 1];
 }
*/

I'm not sure that this idea is enough to qualify for inclusion under the articles section; perhaps I should write up a more-in-depth explanation of this newspost at some point.