Improvements to my Linux Desktop

I’ve gotten a bunch of comments and feedback about my Linux switch, so I thought I’ll write a followup post about how it’s been about 2 months in. Just a disclaimer that this is from the PoV of a long-time Mac user, and most of my irritants are about going back to my familiar defaults for which I almost have muscle memory. Luckily, Linux is configurable, and I’ve got it pretty well configured right now.

Look & Feel and Aesthetics

This is one of those things that is very important to me. The default configuration on any Linux is pretty terrible when you compare it to a Mac, but there’s a lot you can do to make it better. Here’s how my current desktop looks like.

Yup, that is indeed Pop!_OS

What did I do to make it look like this? Well, I pretty much followed this guide exactly. This does mean replacing the default Gnome desktop with XFCE, but that’s pretty much required if you want a Global menu like on the Mac. It does feel a bit too much aping the Mac tbh, but that’s much better than most distro defaults.

Here’s two other things I did in addition to that guide:

I made a workspace switcher panel with this configuration, that provides me some nice icons on the bar, and lets me move between virtual desktops much easier.

I put 4 Emoji icons, largely picked at random to differentiate between the desktops.
You need to pad the name like this otherwise there isn’t sufficient padding in the panel.
Finally, here’s the panel configuration. Nothing special, I just disabled Show miniature view.

Frankly, this is one feature I’ve always liked on Linux deskops. There is of course Spaces on the Mac, but it feels too overwhelming sometimes, and you lose touch of where you are.

I also copied over the SF Pro fonts from my Mac, and made that the default UI font. Even then, the font rendering on Linux can still use some work.

Honestly the aliasing doesn’t seem to matter much.

Keyboard Shortcuts

So I really tried to get used to the Linux shortcuts. But honestly they are just terrible. I mean why use your pinky-finger Control as the primary modifier key? You are just setting yourself up to get carpel tunnel. And if you are going to have a keyboard shorcut, at least make it uniform across apps. Ctrl + V to paste everywhere, except the terminal, where it’s Ctrl + Shift + V 😠. So I found Autokey, and set it up to port over my favourite macOS shortcuts:

This is just a bit of work to set up at first, but then works really well.
If you use the terminal in VS Code, you need to remap Copy and Paste to this, coz that’s the only way to make Super+C/V work in the VS Code editor and its inbuilt terminal.

This works awesome! Except in a few cases when it doesn’t. Autokey seems to sometimes stop working when you have 2 shortcuts configured differently based on window scope. So at times my Super + C stops working in the Terminal. I’m yet to figure out a solid solution for this.


The state of emojis on Linux is… terrible. There are many half-complete solutions and none really work well. The sort of best I’ve found is this onscreen keyboard called Onboard & an emoji layout for that:

Yes, compared to what’s available on macOS, this is just terrible emojis all around. Sigh.

I’ve also mapped it to the same Ctrl + Super + Space shortcut on the Mac:

You can also see my other shortcuts, Ctrl + Super + Q and Shift +Super + 4 for e.g.


Here are a few native apps I’ve found interesting!

Wonderwall to download wallpapers:

LibreOffice which is quite nice right now!

The inbuilt Gnome Calendar app which is… serviceable, but the Mac equivalent isn’t too much better.

A native Spotify client!

A free VMWare Player that pretty much serves as the Fusion-equivalent on the Mac.

Suffice to say, I don’t miss apps much. I do get a bit envious about the design aspect though. Mac apps culturally are much better groomed.

Well that’s it! Ping me on Twitter for any questions, and I’ll be happy to answer them.

Advanced Typescript: Narrowing & Predicates

In the last part of this series, we talked about how you can infer types in Typescript. In this part, we’ll take a look at a related use-case: making the Typescript compiler happy by narrowing the type of a sum type.

What’s Type Narrowing?

Let’s first describe the problem. In Todoist, you can complete & archive both tasks and sections. Let’s say we model it like this:

type TaskCompletedData = {
    subTasks: Task[]
    showHint: boolean
    cursor?: string
type SectionCompletedData = {
    completedItems: CompletedTask[]
    tasksLeft: number
    showHint: boolean
    cursor?: string

Now it doesn’t really matter what these properties are, I’m just going to use them for illustration, but if we want to build a function to work with a generic completedData variable, it can become hard to “narrow down the type” to what we’re interested in. See for example:

function getCompletedCount(completedData: TaskCompletedData | SectionCompletedData) {
    //how do I make Typescript happy here?

The answer is to use the Javascript in operator to check for existence of properties:

function getCompletedCount(completedData: TaskCompletedData | SectionCompletedData) {
    if ('completedItems' in completedData) {
        return getSectionsCount(completedData)
    } else if ('subTasks' in completedData) {
        return getItemsCount(completedData)
    } else {
        throw `Unexpected value for completedData: ${completedData}`

Why can’t you simply call getSectionsCount direct? That’s because Typescript can’t really guess the type of a variable at compile-time (& there’s no Typescript at runtime). Writing the function in this way means you have adequate guards in place so that when Typescript does its job of compiling down, you still have reasonably safe code when all types are erased. It’s not the prettiest of code for sure, but it’s part of the tradeoff of using a fully-erased type system.

Type Predicates

A counterpart to type narrowing is type assertions and type predicates. Note type assertions are the most commonly abused system to “force” the compiler to understand one type as another. The syntax is pretty simple:

const a = "Hello"
getCount(a as any)

In the above example, we are widening the type of a so that it’s compatible with a possible different type that the getCount function expects. Using as casting in this way is frowned upon and possibly never has a place in a good codebase. Type predicates however, are a safer way to achieve the same thing.

Note: to use type predicates, you need a reasonably newer version of Typescript. But it doess make writing these type guards a bit cleaner. Here’s an assumeNotNull Jest test helper that we have in both the Todoist and Twist codebases.

function assumeNotNull(value: T): asserts value is NonNullable {

The current Jest type definitions for expect(value).not.toBeNull() don’t really communicate with Typescript that subsequent calls to expect(value) will always have value as not null (this is because the first expectation will throw and break the test). Here we’re writing a helper function to make this explicit. The important bit to note here is the asserts value is bit, which is called a type predicate. These type predicates are very flexible, and can be used to nudge Typescript in a lot of different ways.

Now both predicates and type narrowing may seem like extra boilerplate that comes with Typescript adoption, and if you are used to writing Javascript, initialy it definitely does seem like that. I sometimes do think of these as “stuff I do to keep the Typescript compiler happy.” From a longer-term software maintanence perspective though, building a strict tsconfig, and sticking to a well-typed codebase has huge rewards. There’s nothing wrong in prototyping your solution in a .JS file, without even having any types at all. And once you start converting the solution to Typescript, the compiler warnings and errors will guide you to writing more safe code.

Well that’s it for this instalment. See you next time!

Forgiving Yourself

I’ve always been an analytical and self-critical person, and I look at my past behavior and try to find improvements. It’s probably an offshoot of being a programmer: endlessly looking for ways to make code more concise, readable, or faster: in search of the next optimization. When you are programming, this optimization is as easy or as hard as debugging: finding faults in your approach, and more often than not, fixing them. You also have tools available that help you avoid similar mistakes in the future, rigorous testing for example.

In real life, this is messier. The first obstacle of course is achieving a bird’s eye view of the situation. The third-party perspective—that which comes most naturally to a programmer—is much harder to achieve in real life. In fact, even when programming, mental tools such as rubber-duck debugging help. Therapy helps to bridge this gap: talking about what’s troubling you and being asked pointed questions about it, seeking the help of a specialist who can apply CBT for example can often lead you to surprising answers. It’s important to note that therapy (just like rubber-duck debugging) is just a tool: at the end of the day, it’s up to you to process the results of your self-examination, and come out of a cocoon.

One difficult counter-note to looking back at your mistakes is guilt. “Why did I do that silly thing?” “Would have been so much better if I said this.” & the classic: “Maybe next time I should not get so pissed off.” For a long time, I was really good at processing guilt: either shrugging it off as life’s many foibles or thinking of mistakes as an adventure to learn. But when mistakes piled up to a pattern of behaviour, and a few of them led me down some painful turns in life, I was quite out of my depth on how to deal with guilt.

After quite some sessions of soul searching, one of the most effective ways I’ve found to process guilt, move on, and be positive is this: to forgive yourself. It’s also one of those concepts that’s very experiential: it’s easy to say these words, read them, intellectually understand what they mean without really grokking its essence. I realised this when I tried to explain this to a few friends, and couldn’t find the right words to describe the process. So here goes a more preapred version.

Forgiveness is a Work in Progress

Forgiveness is not a magic wand that you wave around to get instant results. Everything I talk about here I’m in the process of examining every week. Some events I deal with better than the rest. It’s important to get into this mindset first: small baby steps to a better you every week. Learning to relive the moment objectively (& eventually to live in the moment objectively) is an ongoing practice. Eventually you ackowledge those things that were not under your control, situations there were, and where you could improve, and learn to understand the difference.

Forgiveness is Unconditional

Probably the hardest lesson for me to learn was that forgiving yourself has to be unconditional. It’s not: “I promise never to do this again” (because you will), or “I had <x> mitigating conditions, I probably would never have done this if that had not been the case”. Either way, you are probably wrong: it will happen again, and probably you’ll do worse. Your forgiveness should be rooted in a mind free from conditions and if clauses: it’s acknowledging that a mistake happened, you have thought upon it, and there is repentance, that’s all there is to it.

Forgiveness is a Pledge to Keep Trying

How many times have you fallen off the wagon? How many times will you decide to go the gym and then change plans? How many times have you had a drink too many? Or become too impatient with friends and family? I think the human condition is that we’ll always keep making mistakes. What’s important is to forgive, and keep trying to be better. It’s easy to mistake this for giving up altogether, in fact it’s the opposite: no matter how many times you forget the gym, or go back to an indisciplined life, you know you will one day be better because you’ll try again.

Forgiveness is Gratitude

Part of forgiving yourself is also learning to acknowledge the duality in every situation. Even the worst incidents have joy. The act of finding nuggets of things you can be grateful for is very empowering. While forgiveness can find you a new path to move forward, gratitude leads the way. Maintaing a gratitude journal is a very powerful tool if you are currently struggling.

Well, that’s it. I realise this post is probably a bit more sappy than you expected from me, but it’s part of a growing realisation that topics surrounding mental health have very little exposure in the engineering world. We’re far too analytical and right brained, and our left brain selves are left starving. And to compound to this, Indians still have a stigma in talking about mental health.

This post is mostly due to conversations over the weekend that I had with Prema and Sanjay, two dear friends and probably the liveliest couple I know. If you are in trouble and need help, ping Prema, she has my personal recommendation.

Here’s a video from our weekend together:

Switching to Linux for Work & Play

I’ve been thinking of replacing my 5-year-old Macbook Pro for a while now, and as always, the easiest thing to do would’ve been to just buy the latest model. Apple also has a lot of exciting stuff planned for the Mac, and it’s a great time to be a Mac user, with Big Sur modernizing the interface quite a bit, and the eventual ability to run iOS apps. Apple is definitely firing on all cylinders.

So why a switch to Linux right now? And why Linux instead of Windows? Here’s a few reasons.

Electron Apps are Taking Over

I couldn’t have contemplated such a switch 5 years ago. Simply because my editor in those days was Textmate, an app built for the Mac. Nowadays it’s VS Code, a cross-platform editor from Microsoft. Then there’s Slack, or Twist, or Todoist, or Hey, everything runs on Electron. And Electron apps can run equally well on Windows or Linux. There’s no special Mac-sauce in my apps anymore.

In fact, this is a strike against Windows as well, because my first thought was to switch to Windows (with WSL2), but again, Electron apps work everywhere.

Development Experience is way better on Linux

You deploy everything to a Linux server, and developing on Linux means there’s always 1:1 parity. Even as a frontend developer, tools such as Docker work natively on Linux whereas it’s a horrendous memory-sucking abomination on the Mac. And Apple has been moving away from making the Mac developer-friendly: with enhanced protections such as SIP & Developer ID (which to be honest are legitimate wins for non-developers) more and more open-source software have stopped working or stopped being supported on the Mac. Apple used to ship with the latest open-source software, but now the bundled versions of most tooling is embarassingly out of date. And instead of supporting or building software such as homebrew, all of Apple’s focus is on its own application ecosystem. I’m again not singularly critical of Apple here: they are one of the few big companies that understand the value of focus, but in my case, this focus works against me & my programming profession.

This is again another strike against Windows because while WSL2 is very exciting, with Microsoft now shipping a Linux kernel and updating it through Windows Update, nothing will ever beat Linux compatibility than… Linux.

There are now “Good Enough” Distros on Linux

Me running VS Code, all Doist apps, Steam, and composing this blog post, all on Linux

I’m using Pop!_OS, an operating system meant for developers, which I suppose is strengthened by the fact that the name has got both an exclamation mark and an underscore 😁. But the interesting thing is, after a few bits of customization, it looks and works almost like a Mac. And it’s much, much snappier than my old Macbook Pro.

I like Pop!_OS also because it comes with sensible defaults, and like all variants of Linux, allows for a lot of customizability. I quickly switched off the native tiling feature, but it’s nice that it’s available, as are tons of other developer-specific customization in gnome-tweak-tool. I also remapped a few keyboard shortcuts to be compatible with my Mac muscle memory.

There are a few shortcomings of course:

  1. Native apps (like the Finder equivalent) are less polished, and some (like Quick Look) are not available at all. I’m hoping this improves soon. Note that most apps (even the Terminal for example) have excellent Electron equivalents. Run that, and you get the same experience you will on a Mac.
  2. I’m really used to the menu bar being at the top. Unfortunately, Gnome doesn’t seem to have a reliable Global menu solution.
  3. Font rendering is still sub-par. I’m not sure what exactly is wrong, but I can certainly see a difference in Linux. I tried several combinations of font smoothing and aliasing to get something decent looking, but it’s sad that out of the box nobody has focused on this important UX yet. Default fonts are also fugly, so I had to install and customize a few replacements, thankfully this is easy to do.
  4. You have to be familiar with the command-line to get things done. This is not a bad thing for me as a developer per se, but just putting it out there for other folks who might be looking to switch. Some GUI apps simply refuse to work (the Pop Shop or the App Store equivalent is a good example), and some require esoteric configuration for some tasks (remapping my extra mouse keys).
  5. Hardware compatibility is still somewhat of an issue. The first WiFi dongle I purchased simply refused to work, and I had to research and chug over some extra money for a Linux compatible one. However, my extremely finicky mouse (Logitech MX Master) worked out of the box. So did my Xbox controller, and most everything else I connected. Pop!_OS also comes with an installer that has my proprietary NVIDIA drivers pre-installed, a big win when it comes to the hell I remember that was installing compatible video drivers earlier. It’s way better than the last time I tried Linux, but things still can be improved. The Mac of course sets the gold standard here: in all the time I’ve plugged stuff into the Mac, I don’t think I’ve ever installed a driver.

Well that’s all I can think of for now. It’s surprising that besides these few warts, Linux works surprisingly well for my use-cases. In fact, besides my muscle memory acting up for a few shortcuts (I’m used to Ctrl+A/Ctrl+E, and Option+arrow keys for line traversal on a Mac), and some ridiculous shortcut key decisions on Linux (why not Super+C and Super+V for a global copy/paste shortcut for god’s sake), I’m reasonably comfortable in Linux even after a couple of days of switching over. I’m still using my old Mac keyboard, and I’ll change that soon, because I’m still not completely used to the Super/Ctrl key switch for most shortcuts.

Again: this is a big strike against Windows. The Windows UI always looked too busy for me. It’s got too much stuff on the screen, and the Start Menu is just horrible. There’s also no Dock, and that’s one UI element that I always want available. I looked into this quite deeply because the last time I was on Windows, I was a big LiteStep/BB4Win enthusiast, and while there are signs of a revival, again: my UI preferences run counter to what Microsoft builds.

You can now Game on Linux

So one of my supposed “Linux will never work for me” deal-breakers was gaming. Not that Macs are suitable for gaming—in fact, quite the opposite—but, if I’m going to switch away from a Mac, I want the benefits of being a gamer. I used to love playing World of Warcraft and Elder Scrolls, and that’s something I’d like to revive, especially in the post-COVID world.

So imagine my surprise when my apathetic googling for “Linux gaming” produced some really surprising results. Apparently, during the time I was ignoring Linux, Valve decided to produce a piece of software called Proton—an almost zero-configuration tool built on top of the venerable Wine, and a new port of DirectX over to Linux called DXVK. So gaming on Linux is now as simple as: install Steam, click a few checkboxes, download a game, and hit Play. It’s remarkable when it works, and some games even perform better on Linux. Note: this is not all games, but every one of the games I like to play works well: ESO, Witcher 3, No Man’s Sky, everything works, and works well. This is honestly an astounding development. Except for this, I would’ve gone the Windows and WSL2 route, and it’s still surprising to me when I get the games to work. And in typical open source fashion, there are now better builds of Proton than what Valve ships in house.

Microsoft should honestly be very afraid. There’s potential that Linux can be a better gaming platform FPS-wise, with most of the next-gen cloud gaming platforms running Linux and if there’s any group of tech folks more sensitive to bigger numbers, it’s the gaming crowd. Meanwhile for me personally it almost seems like the best of both worlds: a Mac-like Unix environment that can run games too. Fricking awesome.

So that’s why I switched. What next?

I know this will come as a surprise to many who know me as the quintessential Apple fanboy but I’ve always been pragmatic in my head 😋. I started searching for an alternative only because my age-old Macbook Pro was getting too slow for development, and quite frankly, a top-of-the-line iMac would have gotten me all of these benefits, except perhaps the gaming bit. But these machines are insanely expensive in India, and cannot be custom-configured to boot. For half the price of a Macbook Pro, I bought an assembled PC off of Amazon, and it has been serving me well. This also doesn’t mean I’ll never switch back to a Mac: there’s a lot to love there, and I definitely want to see how Apple ARM shapes up. But for work and play, it’s Linux as of now. Peace.