Replace a Mouse with a Tablet

2 years ago I bought a tablet (Huion HS611) for playing Osu! but I quickly found out that I suck at it. Drawing is not my thing either, so it was mostly unused.

But then recently my Microsoft Sculpt Mouse broke (single click made double clicks). I have a spare mouse which is very “un-ergonomic”. So I needed another replacement, and I tested again the tablet, but this time as a mouse-replacement.

And it’s better than I expected!

  • Scrolling is very easy (like on a phone).
  • Single-click is super-easy although click-and-drag does not work at all as it’s used for scrolling.
  • Double-click and right-mouse-button-clicks are hard: the pen pointer moves a lot more than when using a mouse.
  • Having the pen in your hand means that when you type, you either drop the pen or you type with a pen between your fingers.

The one problem I had was that the Huion driver puts Windows into “Tablet mode”, which is generally correct. However it also means Windows likes to pop up a soft keyboard. It should not as I have a USB keyboard connected. It’s annoying as heck then it happens. Luckily the fix is simple.

No more soft keyboard pop-up

Run services.msc, find the Touch Keyboard and Handwriting Panel Service and simply disable it.


BASIC Benchmarks

Found this Wikipedia article about BASIC benchmarks and it had some run times for some old computers I used before. E.g. benchmark 7 took 21.1s on a BBC Micro which was particularly fast. A C64 took 47.5s

How long does a current computer take for this kind of work?

I got no BASIC, but JavaScript is kind’a similar: it’s often the first language to learn programming. So let’s see how long that takes (after translating the BASIC program into JavaScript):

function doNothing() {

function bench7() {
    let k = 0;
    let m = [];
    do {
        let a = k / 2 * 3 + 4 - 5;
        for (let l = 0; l < 5; ++l) {
            m[l] = a;
    } while (k < 1000);

function manyBench(n) {
    for (let i=0; i<n; ++i) {


Running this took not that long:

❯ time node benchmark7.js
node benchmark7.js  2.82s user 0.02s system 99% cpu 2.845 total

That’s for 500,000 times though, so each benchmark run takes about 0.056ms on my low-end PC (Ryzen 5 Pro 3400GE). That’s over 3.7M times faster.

And before anyone mentions it: yes, any modern compiler will optimize the whole benchmark away since no useful output or calculation is done. I am not sure how much Node.js (resp. the V8 engine) will remove. Making the code less do-nothing-like and taking the number of loops from the command line did not increase the run time significantly beside what I would have expected from the additional code, so I concluded that the code is executed as-is and parts have not been optimized away.

Winter Project: SDR

It’s still September, but winter will come soon. I learned about Software Defined Radio years ago, but the equipment was very expensive or limited. The software ecosystem wasn’t that large either.

However that has changed in the meantime: an Airspy is US$169 and the SpyVerter $49 and that combo can scan anything from DC to 1.8GHz.

First order was watching the spectrum at various wavelength via SDR#:

Scanning 433MHz band

As you can see it detects nicely when I pushed a button on the 433.92MHz remote control. Interestingly there’s a slight 3kHz miss-tuning for the small remote compared to the RF bridge:

Red line: 433.92MHz.
Lower signal: RF Bridge, upper signal: remote control

And I can listen to FM radio (very poor quality since the antenna I have is for 433MHz band):

FM Radio

To use any other bands beside 433MHz I’d need different or adjustable antennas. Something for later to worry about.

Decoding the RF Remote Control

Recognizing and decoding digital signals is surprisingly easy with the right tools. rtl_433 can decode a lot of standard devices, so it was my first attempt. Compiling was a bit more complex than I though due to dependencies. It was well enough documented though. After that’s done, time to decode some signals!

rtl_433 -d airspy -Y autolevel -M level -R 0 -v -A

This shows output like this for every button press and usually several times repeated:

Analyzing pulses...
Total count:   25,  width: 290297.52 ms         (72574379 S)
Pulse width distribution:
 [ 0] count:    1,  width: 289230564 us [289230564;289230564]   (72307641 S)
 [ 1] count:   16,  width: 11380 us [11372;11456]       (2845 S)
 [ 2] count:    8,  width: 33640 us [33636;33644]       (8410 S)
Gap width distribution:
 [ 0] count:    9,  width: 11708 us [11684;11728]       (2927 S)
 [ 1] count:   15,  width: 34020 us [33880;34284]       (8505 S)
Pulse period distribution:
 [ 0] count:    1,  width: 289242292 us [289242292;289242292]   (72310573 S)
 [ 1] count:   23,  width: 45380 us [45256;45660]       (11345 S)
Pulse timing distribution:
 [ 0] count:    1,  width: 289230564 us [289230564;289230564]   (72307641 S)
 [ 1] count:   25,  width: 11496 us [11372;11728]       (2874 S)
 [ 2] count:   23,  width: 33888 us [33636;34284]       (8472 S)
 [ 3] count:    1,  width: 100004 us [100004;100004]    (25001 S)
Level estimates [high, low]:    126,     -1
RSSI: -42.3 dB SNR: 42.0 dB Noise: -84.3 dB
Frequency offsets [F1, F2]:     121,      0     (+0.5 kHz, +0.0 kHz)
Guessing modulation: Pulse Width Modulation with sync/delimiter
view at
Attempting demodulation... short_width: 11380, long_width: 33640, reset_limit: 34288, sync_width: 289230560
Use a flex decoder with -X 'n=name,m=OOK_PWM,s=11380,l=33640,r=34288,g=0,t=0,y=289230560'
pulse_slicer_pwm(): Analyzer Device
bitbuffer:: Number of rows: 1
[00] {24} fb 0e 7b  : 11111011 00001110 01111011

Note the line with the flex decoder: those numbers vary slightly. Take the average for s, for l and r. Use the most common values for g and y. Use t to add some margin of timing error. I have 2 RF devices: a small remote control and a RF-Wifi bridge. Both can send the same signals, but their timing is slightly off:

  • Remote: s=13378±5, l=33638±10, r=34929±10
  • Bridge: s=11635±40, l=34198±10, r=34768±10

So I took the average, added some slack and that worked for both devices:

$ rtl_433 -d airspy -Y autolevel -M level -R 0 -v -X 'n=remote1,m=OOK_PWM,s=11500,l=34000,r=34900,g=0,t=800,bits=24'
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
time      : 2022-09-23 11:46:45
model     : name         count     : 1             num_rows  : 2             rows      :
len       : 0            data      : ,
len       : 24           data      : fb0e7b
codes     : {0}, {24}fb0e7b
Modulation: ASK          Freq      : 433.9 MHz
RSSI      : -48.3 dB     SNR       : 36.0 dB       Noise     : -84.3 dB
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
time      : 2022-09-23 11:46:45
model     : name         count     : 1             num_rows  : 2             rows      :
len       : 0            data      : ,
len       : 24           data      : fb0e7b
codes     : {0}, {24}fb0e7b
Modulation: ASK          Freq      : 433.9 MHz
RSSI      : -48.3 dB     SNR       : 36.0 dB       Noise     : -84.3 dB
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

Note that the shown code fb0e7b is unique to a single button. The other 3 buttons send different 24 bit codes.

Other 433.92MHz Devices

I was hoping to find more devices on that band, but I guess I need a really good antenna on a roof to detect them.

Frequency Usage in Japan

Here is the most useful general frequency usage plan.

OverTheWire – Security Wargames

I wish I had seen this a long time ago: are a set of challenges in the area of security: buffer overflows, command injection, web server security, plus fun Linux command line skills.

I learned a lot about how to detect and exploit buffer overflows and shellcode in the Narnia wargame. Good fun and educational too.

Natas is nice since it’s using a web server while the other ones use ssh. The harder ones are…hard, but there’s solutions all over the web in case you get stuck.

All in all, highly recommended to see how easy it is to exploit badly written code and how to write code which does not repeat those known security problems.

Dart & Pool

Tip of the day: If you use Dart and want to use the Pool library, expect not much help from Google when searching for those keywords: you get the expected results. Adding “future” or “async” helps.

Anyway, the point of this post is a small example how to use a Pool to run commands in parallel, but not too many concurrently.

import 'dart:io';
import 'package:pool/pool.dart';
import 'package:test/test.dart';

Future<ProcessResult> runCommand(String command, List<String> args) {
  return, args);

void main() {
  test('Run 20 slow date commands', () async {
    var pool = Pool(5);
    List<Future<ProcessResult>> results = [];
    for (var i = 0; i < 20; ++i) {
      results.add(pool.withResource(() => runCommand('./date_slow', ['+%N'])));
    await pool.done;
    for (var process in results) {
      var res = await process;

./date_slow is a simple script which returns something on STDOUT and finishes in 2s:

date $*
sleep 2

What happens then is that Pool(5) creates a pool with 5 slots. The first for loop tries to run 20 commands though and it’ll queue all 20 immediately, but only 5 at most will run. The rest simply waits until it’s their turn.

pool.close() stops any new entries and the await pool.done simply waits until the pool is closed and all jobs are executed.

The 2nd for loop (with the print() statement) uses await to get the ProcessResult from the Future<ProcessResult> which returns and which is store in the results[] list.

The outcome here is that if the pool is 5 slots large, and each command runs for 2 seconds, the complete set of 20 jobs runs in 20/5*2=8 seconds. If I make the pool 10 slots large, it’ll run in 20/10*2=4 seconds and in case of 20 or more slots, it’ll be 2 seconds. And it’ll never run more processes than slots are available.

Why I need this? I have a list of URLs from few to hundreds which I need to query. While I can query many concurrently, each instance takes up some non-trivial amount of memory since it’s an external program. Currently at 100 concurrent calls, it uses up all memory on a 16GB RAM notebook. While there are many ways to work around this (do one at a time is the safest and slowest one), using a pool is perfect: it runs many commands in parallel, but I can limit the number of how many run in parallel.

Twitter Does Not Like Me Anymore

All the buzz about Twitter and Elon made me look at my Twitter profile. I saw the missing date of birth, so I added it. Now my account is locked:

Here I became less than 13 years old

I am quite confident I put in a 19xx year in there, so I should be at least 22 years old, but I cannot check that now since my account is locked.

At this point I wonder if I should care. At worst in 13 years I’ll get my access back. If you send me a message in Twitter, you might have to wait 13 years for a reply…

Update 2022-07-31: Well, they like me again. It seems I managed to become 13 quite fast:

And days later I aged enough to be over 13!

Fake Webpages And How To Detect Them

Being in Japan and knowing how much good knifes cost, when I saw an advertising of hand made Japanese knifes…let’s say that it looked suspicious from the start. Their web page did look good though. But still…suspicious. Starting with the name “Huusk” which is as much non-Japanese as I can imagine.

Then the commonly seen signs to create some time pressure:

70% discount!

and the popups about sales happening right now:

Someone in Kyoyo Kita-ku Minamimitakamine bought it!

and yet that number of knifes left does not change:

7 left. Always.

Typical scammer stuff of creating a sense of “Buy it now! Before you start to think about it!”

I looked once up a countdown on another web page which counted from about 3h down to zero…so I let it run out. 3h later it showed negative time. And if you reload the page, it goes back to about 3h! This one is less obvious, but I was curious how the popup gets populated as it tries to imply a “Someone near you bought something, so it must be good!” Is it hard-coded like the timer, or dynamically pulled from somewhere? Turns out it is statically populated:

  "orders": [{
    "first_name": "hidetaka",
    "city": "funakosityo yokosuka",
    "country": "JP",
    "topText": "hidetaka from Funakosityo yokosuka, JP made a purchase.",
    "bottomText": "X1 Huusk Knife Sold!",
  }, {
    "first_name": "Indrajith",
    "city": "Itakoshi,Hinode",
    "country": "JP",
    "topText": "Indrajith from Itakoshi,Hinode, JP made a purchase.",
    "bottomText": "X1 Huusk Knife Sold!",
  }, {
    "first_name": "YOICHI",
    "city": "Nagano inaba",
    "country": "JP",
    "topText": "YOICHI from Nagano inaba, JP made a purchase.",
    "bottomText": "X1 Huusk Knife Sold!",
  }, {
    "first_name": "TAKASHIGE",
    "country": "JP",
    "bottomText": "X3 Huusk Knives Sold!",
  }, {
    "first_name": "YASUHIRO",
    "city": "Ootaku SINKAMATA",
    "country": "JP",
    "topText": "YASUHIRO from Ootaku SINKAMATA, JP made a purchase.",
    "bottomText": "X3 Huusk Knives Sold!",
  }, {
    "first_name": "hiroshi",
    "city": "nakagawashimatsunoki",
    "country": "JP",
    "topText": "hiroshi from Nakagawashimatsunoki, JP made a purchase.",
    "bottomText": "X4 Huusk Knives Sold!",
  }, {
    "first_name": "EIJI",
    "city": "MATUBARACITY",
    "country": "JP",
    "topText": "EIJI from MATUBARACITY, JP made a purchase.",
    "bottomText": "X4 Huusk Knives Sold!",
  }, {
    "first_name": "Kyle",
    "city": "Okayama",
    "country": "JP",
    "topText": "Kyle from Okayama, JP made a purchase.",
    "bottomText": "X3 Huusk Knives Sold!",
  }, {
    "first_name": "Syouji",
    "city": "Yokohamasi",
    "country": "JP",
    "topText": "Syouji from Yokohamasi, JP made a purchase.",
    "bottomText": "X4 Huusk Knives Sold!",
  }, {
    "first_name": "Toshio",
    "city": "hamamatsu",
    "country": "JP",
    "topText": "Toshio from Hamamatsu, JP made a purchase.",
    "bottomText": "X3 Huusk Knives Sold!",
  "image": "",

The Terms and Conditions page is suspicious too:

Hand-made knifes and they create a single size? Why would anyone limit themselves to a single size if they are hand-made anyway? Japanese love to have different knifes for different jobs, but this Japanese company does not?

If you look up the text of some of the user testimonials you’ll find another knife company “Kaitomi” which looks just the same. But their web page is not yet ready: Still references to Huusk. Oops! At least it sounds a bit more (fake) Japanese…

Oops…forgot to remove the Huusk stuff

And I even found the popular Lorem Ipsum text filler:

So it’s pretty clear that this is a scam.


I looked up Huusk expecting reviews like “Those knifes are not as good as the advertising suggested”, but I found something even better! And confirmed my findings. And there’s many more such pages are discussed! E.g. fake investment web pages who’ll take your money and predictably disappear.

The most interesting part of that web page however is that they basically explain what to look for:

  • DNS and company registration, country and date. Recently registered and claiming to be 20 years in business?
  • Actual location of the business: Japan? US? UK? Lithuania? Nigeria? Claiming or suggesting that they are from somewhere else?
  • Use of stock photos for “user testimonials”. Reverse image search can find stock photos.
  • Copy&paste user testimonials used in other places too.
  • Selling a “unique” product which is also sold on other places (eBay, Aliexpress, Taobao).
  • The intense attempt of creating a sense of urgency.

Very educational and I wish more people would be aware of those scam tricks.