Full Width [alt+shift+f] Shortcuts [alt+shift+k]
Sign Up [alt+shift+s] Log In [alt+shift+l]
119
The X220 ThinkPad is the Best Laptop in the World 2023-09-26 The X220 ThinkPad is the greatest laptop ever made and you're wrong if you think otherwise. No laptop hardware has since surpassed the nearly perfect build of the X220. New devices continue to get thinner and more fragile. Useful ports are constantly discarded for the sake of "design". Functionality is no longer important to manufacturers. Repairability is purposefully removed to prevent users from truly "owing" their hardware. It's a mess out there. But thank goodness I still have my older, second-hand X220. Specs Before I get into the details explaining why this laptop is the very best of its kind, let's first take a look at my machine's basic specifications: CPU: Intel i7-2640M (4) @ 3.500GHz GPU: Intel 2nd Generation Core Processor Memory: 16GB DDR3 OS: Arch Linux / OpenBSD Resolution: 1366x768 With that out of the way, I will break down my thoughts on the X220 into five major sections: Build quality, available ports, the...
a year ago

Improve your reading experience

Logged in users get linked directly to articles resulting in a better reading experience. Please login for free, it takes less than 1 minute.

More from Making software better without sacrificing user experience.

Bringing dwm Shortcuts to GNOME

Bringing dwm Shortcuts to GNOME 2023-11-02 The dwm window manager is my standard "go-to" for most of my personal laptop environments. For desktops with larger, higher resolution monitors I tend to lean towards using GNOME. The GNOME DE is fairly solid for my own purposes. This article isn't going to deep dive into GNOME itself, but instead highlight some minor configuration changes I make to mimic a few dwm shortcuts. For reference, I'm running GNOME 45.0 on Ubuntu 23.10 Setting Up Fixed Workspaces When I use dwm I tend to have a hard-set amount of tags to cycle through (normally 4-5). Unfortunately, dynamic rendering is the default for workspaces (ie. tags) in GNOME. For my personal preference I set this setting to fixed. We can achieve this by opening Settings > Multitasking and selecting "Fixed number of workspaces". Screenshot of GNOME's Multitasking Settings GUI Setting Our Keybindings Now all that is left is to mimic dwm keyboard shortcuts, in this case: ALT + $num for switching between workspaces and ALT + SHIFT + $num for moving windows across workspaces. These keyboard shortcuts can be altered under Settings > Keyboard > View and Customize Shortcuts > Navigation. You'll want to make edits to both the "Switch to workspace n" and "Move window to workspace n". Screenshot of GNOME's keyboard shortcut GUI: switch to workspace Screenshot of GNOME's keyboard shortcut GUI: move window to workspace That's it. You're free to include even more custom keyboard shortcuts (open web browser, lock screen, hibernate, etc.) but this is a solid starting point. Enjoy tweaking GNOME!

a year ago 93 votes
Installing Older Versions of MongoDB on Arch Linux

Installing Older Versions of MongoDB on Arch Linux 2023-09-11 I've recently been using Arch Linux for my main work environment on my ThinkPad X260. It's been great. As someone who is constantly drawn to minimalist operating systems such as Alpine or OpenBSD, it's nice to use something like Arch that boasts that same minimalist approach but with greater documentation/support. Another major reason for the switch was the need to run older versions of "services" locally. Most people would simply suggest using Docker or vmm, but I personally run projects in self-contained, personalized directories on my system itself. I am aware of the irony in that statement... but that's just my personal preference. So I thought I would share my process of setting up an older version of MongoDB (3.4 to be precise) on Arch Linux. AUR to the Rescue You will need to target the specific version of MongoDB using the very awesome AUR packages: yay -S mongodb34-bin Follow the instructions and you'll be good to go. Don't forget to create the /data/db directory and give it proper permissions: mkdir -p /data/db/ chmod -R 777 /date/db What About My "Tools"? If you plan to use MongoDB, then you most likely want to utilize the core database tools (restore, dump, etc). The problem is you can't use the default mongodb-tools package when trying to work with older versions of MongoDB itself. The package will complain about conflicts and ask you to override your existing version. This is not what we want. So, you'll have to build from source locally: git clone https://github.com/mongodb/mongo-tools cd mongodb-tools ./make build Then you'll need to copy the built executables into the proper directory in order to use them from the terminal: cp bin/* /usr/local/bin/ And that's it! Now you can run mongod directly or use systemctl to enable it by default. Hopefully this helps anyone else curious about running older (or even outdated!) versions of MongoDB.

a year ago 59 votes
Converting HEIF Images with macOS Automator

Converting HEIF Images with macOS Automator 2023-07-21 Often times when you save or export photos from iOS to iCloud they often render themselves into heif or heic formats. Both macOS and iOS have no problem working with these formats, but a lot of software programs will not even recognize these filetypes. The obvious step would just be to convert them via an application or online service, right? Not so fast! Wouldn't it be much cleaner if we could simply right-click our heif or heic files and convert them directly in Finder? Well, I've got some good news for you... Basic Requirements You will need to have Homebrew installed You will need to install the libheif package through Homebrew: brew install libheif Creating our custom Automator script For this example script we are going to convert the image to JPG format. You can freely change this to whatever format you wish (PNG, TIFF, etc.). We're just keeping things basic for this tutorial. Don't worry if you've never worked with Automator before because setting things up is incredibly simple. Open the macOS Automator from the Applications folder Select Quick Option from the first prompt Set "Workflow receives current" to image files Set the label "in" to Finder From the left pane, select "Library > Utilities" From the presented choices in the next pane, drag and drop Run Shell Script into the far right pane Set the area "Pass input" to as arguments Enter the following code below as your script and type ⌘-S to save (name it something like "Convert HEIC/HEIF to JPG") for f in "$@" do /opt/homebrew/bin/heif-convert "$f" "${f%.*}.jpg" done Making Edits If you ever have the need to edit this script (for example, changing the default format to png), you will need to navigate to your ~/Library/Services folder and open your custom heif Quick Action in the Automator application. Simple as that. Happy converting! If you're interested, I also have some other Automator scripts available: Batch Converting Images to webp with macOS Automator Convert Files to HTML with macOS Automator Quick Actions

a year ago 33 votes
Blogging for 7 Years

Blogging for 7 Years 2023-06-24 My first public article was posted on June 28th 2016. That was seven years ago. In that time, quite a lot has changed in my life both personally and professionally. So, I figured it would be interesting to reflect on these years and document it for my own personal records. My hope is that this is something I could start doing every 5 or 10 years (if I can keep going that long!). This way, my blog also serves as a "time capsule" or museum of the past... Fun Facts This Blog: I originally started blogging on bradleytaunt.com using WordPress, but since then I have changed both my main domain and blog infrastructure multiple times. At a glance I have used: Jekyll Hugo Blot Static HTML/CSS PHPetite Shinobi pblog barf Currently using! Personal: As with anyone over time, the personal side of my life has seen the biggest updates: Married the love of my life (after knowing each other for ~14 years!) Moved out into rural Ontario for some peace and quiet Had three wonderful kids with said wife (two boys and a girl) Started noticing grey sprinkles in my stubble (I guess I can officially call myself a "grey beard"?) Professionally: Pivoted heavily into UX research and design for a handful of years (after working mostly with web front-ends) Recently switched back into a more fullstack development role to challenge myself and learn more Nothing Special This post isn't anything ground-breaking but for me it's nice to reflect on the time passed and remember how much can change in such little time. Hopefully I'll be right back here in another 7 years and maybe you'll still be reading along with me!

over a year ago 55 votes

More in programming

Why learn about the golden-section search

An interactive demo of bisection search and golden ratio search algorithms. There is also a motivation to learn them both. Spoiler alert! One converges better, and the other has a better computational cost.

21 hours ago 3 votes
The parental dead end of consent morality

Consent morality is the idea that there are no higher values or virtues than allowing consenting adults to do whatever they please. As long as they're not hurting anyone, it's all good, and whoever might have a problem with that is by definition a bigot.  This was the overriding morality I picked up as a child of the 90s. From TV, movies, music, and popular culture. Fly your freak! Whatever feels right is right! It doesn't seem like much has changed since then. What a moral dead end. I first heard the term consent morality as part of Louise Perry's critique of the sexual revolution. That in the context of hook-up culture, situationships, and falling birthrates, we have to wrestle with the fact that the sexual revolution — and it's insistence that, say, a sky-high body count mustn't be taboo — has led society to screwy dating market in the internet age that few people are actually happy with. But the application of consent morality that I actually find even more troubling is towards parenthood. As is widely acknowledged now, we're in a bit of a birthrate crisis all over the world. And I think consent morality can help explain part of it. I was reminded of this when I posted a cute video of a young girl so over-the-moon excited for her dad getting off work to argue that you'd be crazy to trade that for some nebulous concept of "personal freedom". Predictably, consent morality immediately appeared in the comments: Some people just don't want children and that's TOTALLY OKAY and you're actually bad for suggesting they should! No. It's the role of a well-functioning culture to guide people towards The Good Life. Not force, but guide. Nobody wants to be convinced by the morality police at the pointy end of a bayonet, but giving up on the whole idea of objective higher values and virtues is a nihilistic and cowardly alternative. Humans are deeply mimetic creatures. It's imperative that we celebrate what's good, true, and beautiful, such that these ideals become collective markers for morality. Such that they guide behavior. I don't think we've done a good job at doing that with parenthood in the last thirty-plus years. In fact, I'd argue we've done just about everything to undermine the cultural appeal of the simple yet divine satisfaction of child rearing (and by extension maligned the square family unit with mom, dad, and a few kids). Partly out of a coordinated campaign against the family unit as some sort of trad (possibly fascist!) identity marker in a long-waged culture war, but perhaps just as much out of the banal denigration of how boring and limiting it must be to carry such simple burdens as being a father or a mother in modern society. It's no wonder that if you incessantly focus on how expensive it is, how little sleep you get, how terrifying the responsibility is, and how much stress is involved with parenthood that it doesn't seem all that appealing! This is where Jordan Peterson does his best work. In advocating for the deeper meaning of embracing burden and responsibility. In diagnosing that much of our modern malaise does not come from carrying too much, but from carrying too little. That a myopic focus on personal freedom — the nights out, the "me time", the money saved — is a spiritual mirage: You think you want the paradise of nothing ever being asked of you, but it turns out to be the hell of nobody ever needing you. Whatever the cause, I think part of the cure is for our culture to reembrace the virtue and the value of parenthood without reservation. To stop centering the margins and their pathologies. To start centering the overwhelming middle where most people make for good parents, and will come to see that role as the most meaningful part they've played in their time on this planet. But this requires giving up on consent morality as the only way to find our path to The Good Life. It involves taking a moral stance that some ways of living are better than other ways of living for the broad many. That parenthood is good, that we need more children both for the literal survival of civilization, but also for the collective motivation to guard against the bad, the false, and the ugly. There's more to life than what you feel like doing in the moment. The worst thing in the world is not to have others ask more of you. Giving up on the total freedom of the unmoored life is a small price to pay for finding the deeper meaning in a tethered relationship with continuing a bloodline that's been drawn for hundreds of thousands of years before it came to you. You're never going to be "ready" before you take the leap. If you keep waiting, you'll wait until the window has closed, and all you see is regret. Summon a bit of bravery, don't overthink it, and do your part for the future of the world. It's 2.1 or bust, baby!

17 hours ago 2 votes
AmigaGuide Reference Library

As I slowly but surely work towards the next release of my setcmd project for the Amiga (see the 68k branch for the gory details and my total noob-like C flailing around), I’ve made heavy use of documentation in the AmigaGuide format. Despite it’s age, it’s a great Amiga-native format and there’s a wealth of great information out there for things like the C API, as well as language guides and tutorials for tools like the Installer utility - and the AmigaGuide markup syntax itself. The only snag is, I had to have access to an Amiga (real or emulated), or install one of the various viewer programs on my laptops. Because like many, I spend a lot of time in a web browser and occasionally want to check something on my mobile phone, this is less than convenient. Fortunately, there’s a great AmigaGuideJS online viewer which renders AmigaGuide format documents using Javascript. I’ve started building up a collection of useful developer guides and other files in my own reference library so that I can access this documentation whenever I’m not at my Amiga or am coding in my “modern” dev environment. It’s really just for my own personal use, but I’ll be adding to it whenever I come across a useful piece of documentation so I hope it’s of some use to others as well! And on a related note, I now have a “unified” code-base so that SetCmd now builds and runs on 68k-based OS 3.x systems as well as OS 4.x PPC systems like my X5000. I need to: Tidy up my code and fix all the “TODO” stuff Update the Installer to run on OS 3.x systems Update the documentation Build a new package and upload to Aminet/OS4Depot Hopefully I’ll get that done in the next month or so. With the pressures of work and family life (and my other hobbies), progress has been a lot slower these last few years but I’m still really enjoying working on Amiga code and it’s great to have a fun personal project that’s there for me whenever I want to hack away at something for the sheer hell of it. I’ve learned a lot along the way and the AmigaOS is still an absolute joy to develop for. I even brought my X5000 to the most recent Kickstart Amiga User Group BBQ/meetup and had a fun day working on the code with fellow Amigans and enjoying some classic gaming & demos - there was also a MorphOS machine there, which I think will be my next target as the codebase is slowly becoming more portable. Just got to find some room in the “retro cave” now… This stuff is addictive :)

4 hours ago 2 votes
Custom search UI in CodeMirror 6 and Svelte 5

CodeMirror 6 has @codemirror/search package which provides UI for searching within a document, triggered via Ctrl + F. In my note-taking application Edna I wanted something slightly different. This article describes how I implemented it. The UI went from: to: CodeMirror is very customizable which is great, but makes it hard to understand how to put the pieces together to achieve desired results. Almost all of the work is done in @codemirror/search, I just plugged my own UI into framework designed by the author of CodeMirror. How to get standard search UI in CodeMirror When you create CodeMirror you configure it with: import { highlightSelectionMatches, searchKeymap, } from "@codemirror/search"; EditorState.create({ // ... other stuff extensions: [ // ... other stuff highlightSelectionMatches(), keymap.of([ // ... other stuff ...searchKeymap, ]), ] }) searchKeymap is what registers key bindings like Ctrl + F to invoke search UI, F3 to find next match etc. highlightSelectionMatches is an extension that visually highlights search matches. Customizing the UI CodeMirror 6 has a notion of UI panels. Built-in search UI is a panel. Custom search UI panel Thankfully panel is as generic as it can be: it’s just a div hosting the UI. The author predicted the need for providing custom search UI so it’s as easy as adding search extension configured with custom search panel creation function: import { search, } from "@codemirror/search"; function createFindPanel() { ... } EditorState.create({ // ... other stuff extensions: [ // ... other stuff search({ createPanel: createFnddPanel, }), ] }) All the options to search() are documented here. Create the panel Function that creates the panel returns a DOM element e.g. a <div>. You can create that element using vanilla JavaScript or using a framework like Svelte, React, Vue. For Svelte the trick is to manually instantiate the component. I use Svelte 5 so I’ve created Find.svelte component which floats over the editor area thanks to position: fixed. Here’s how to manually mount it: import Find from "../components/Find.svelte"; import { mount } from "svelte"; function createFnddPanel(view) { const dom = document.createElement("div"); const args = { target: dom, props: { view, }, }; mount(Find, args); return { dom, top: true, }; } If you provide createPanel function, @codemirror/search will call it to create search UI instead of its own. It’s a great design because it reuses most of the code in @codemirror/search. The UI can be triggered programmatically, by calling openSearchPanel(EditorView) (and closed with closeSearchPanel(EditorView). Or By Mod + F key binding defined in searchKeymap. You can change the binding by not including searchKeymap and instead provide your own array of bindings to functions from @codemirror/search. By default CodeMirror shrinks the editor area to host the UI. It can host it either at the top or the bottom of the editor, which is what top return value indicates. In my case value of top doesn’t matter because my UI floats on top of editor with position: fixed and z-index: 20 so we don’t shrink the editor area. The DOM element you create is hosted within this structure: <div class="cm-panels cm-panels-top" style="top: 0px;"> <div class="cm-panel"> <!-- YOUR DOM ELEMENT --> </div> </div> My CSS provided with EditorView.theme() was: const themeBase = EditorView.theme({ ".cm-panels .cm-panel": { boxShadow: "0 0 10px rgba(0,0,0,0.15)", padding: "8px 12px", }, }); The padding made the wrapper element visible even though I didn’t want it. To fix it I simply changed it to: EditorView.theme({ ".cm-panels .cm-panel": { }, }); Doing the searches When user changes the text in input field, we need tell CodeMirror 6 to do the search. You talk to CodeMirror using those commands. To start a new search you do: let query = new SearchQuery({ search: searchTerm, replace: replaceTerm, // if you're going to run replacement commands caseSensitive: false, literal: true, }); view.dispatch({ effects: setSearchQuery.of(query), }); CodeMirror 6 supports regex search, matching case, matching only whole world option. See SearchQuery docs. To instruct CodeMirror to navigate to next, previous match etc. you call: findNext : advance to next match in the editor findPrevious : go to previous match replaceNext : replace next match replaceAll : replace all matches All those function take EditorView as an argument and act based on the last SearchQuery. All commands are documented here. Doing it in Svelte 5 Here’s the core of the component: <div class="flex"> <input bind:this={searchInput} type="text" spellcheck="false" placeholder="Find" bind:value={searchTerm} class="w-[32ch]" use:focus onkeydown={onKeyDown} /> <button onclick={next} title="find next (Enter}">next</button> <button onclick={prev} title="find previous (Shift + Enter)">prev </button> <button onclick={all} title="find all">all </button> </div> <div class="flex"> <input type="text" spellcheck="false" placeholder="Replace" bind:value={replaceTerm} class="w-[32ch]" /> <button onclick={replace}>replace</button> <button onclick={_replaceAll} class="grow">all</button> </div> We do “search as you type” by observing changes to searchTerm input field: $effect(() => { let query = new SearchQuery({ search: searchTerm, replace: replaceTerm, caseSensitive: false, literal: true, }); view.dispatch({ effects: setSearchQuery.of(query), }); }); On button press we invoke desired functionality, like: function next() { findNext(view); } Pre-populating input from selection When we show search UI it’s nice to pre-populate search term with current selection. It’s as easy as: import { getSearchQuery, } from "@codemirror/search"; let query = getSearchQuery(view.state); searchTerm = query.search; This must be done on component initialization, not in onMount(). As addition trick, we select the content of input field: onMount(() => { tick().then(() => { searchInput.select(); }); }); Closing search panel on Esc I wanted to hide search UI when Escape key is pressed. Thankfully we get searchPanelOpen(EditorView) function that tells us if search panel is open and closeSearchPanel(EditorView) to close. So it’s as easy as: function onKeyDown(ev) { if (ev.key === "Escape") { let view = getEditorView(); if (view && searchPanelOpen(view.state)) { closeSearchPanel(view); return; } } } Customizing the look of search matches CodeMirror 6 is built on web technologies so the way it allows customizing the look of things is by applying known CSS styles. You provide your own CSS to change the look of things. Here’s the CSS for search matches: <!-- this is how all matches are highlighted --> <span class="cm-searchMatch"><span class="cm-selectionMatch">another</span></span> <!-- this is how currently selected match is higlighted. It changes with findNext() / findPrevious() / selectMatches() --> <span class="cm-searchMatch cm-searchMatch-selected">another</span> How to figure things out When I started working on this I did not know any of the above. Here’s my strategy for figuring this out. Look at the source code We live in open source world. The code to @codemirror/search is available so the first step was to look at it to see exported APIs etc. Look at the docs Ok, not really. I knew so little that even though CodeMirror has extensive documentation, I just couldn’t figure out how to put the pieces together. Ask omniscient AI I saw that there is setSearchQuery API but I didn’t know how to use it. I asked Grok: how to use setSearchQuery from @codemirror/search package in codemirror 6 It gave me a good response. Look at the code again So I tried sending new SearchQuery to the editor and it didn’t work i.e. I didn’t see the matches highlighted. Back to reading the code and I see that in searchHighlighter higlight() function, it doesn’t do anything if there’s no panel. But I want my own UI, not their panel, so hmm… See how others did it Surely there must be some open source project that did something similar. The trick is to find it. I used GitHub code search to look for distinct APIs, which is harder than it looks. If you search for findNext you’ll be flooded with results. So I searched for uses of @codemirror/search. I found a few projects that created custom search UIs and that gave me enough hints on how to use the APIs and how to put all the available pieces together. Resources Edna is a note taking application for developers and power users documentation of @codemirror/search my full implementation is in Find.svelte another implementation in Vue another implementation in vanilla JavaScript

yesterday 2 votes
Reproducing Espressif’s reset circuit

I recently discussed how Espressif implements automatic reset, a feature that lets users easily update the code on an Espressif microcontroller. There are actually more subtleties than a quick look would suggest, and I spent a fair bit of time investigating them. This article and the next two present what I have learned. The current … Continue reading Reproducing Espressif’s reset circuit → The post Reproducing Espressif’s reset circuit appeared first on Quentin Santos.

2 days ago 4 votes