A peek at the old win32 APIs
Latest update:
I wanted to write a simple GUI for HKCU\Control Panel\Desktop\WindowMetrics
key. Until now Windows had a
dialog that allowed
fonts/colours/etc tweaking for the current 'theme', but as soon as the
name of w10 became synonymous with all that is terrible, the dialog
was removed in r1703.
What's the easiest way to write a similar dialog? I don't mind the
default colours but I don't like the default font sizes. What if we
could draw fake controls using html & provide a simple form to modify
the appearance of the elements in the RT? It feels like a trivial
task.
Obviously we would need a way to do get/set operations on the
registry. What options do we have?
I didn't want to employ Electron. That one time I've tried to write a
commercial peace of software with it, it brought me nothing but
irritation. It's also a great way to become a mockery on HN/reddit:
every time some pour soul posts about their little app written in
JS+Electron, they receive so much shit from the 'real' programmers for
not writing it in QT or something native to the platform, that most
readers immediately start feeling sorry for the OP.
If not Electron then what? The 'new' Microsoft way is to use UWP API,
that strongly reminds me of the Electron concept, only done in a vapid
style. I didn't want to touch it with a 3.048 m pole.
What about plain Node + a browser? We can write a server in node that
communicates with the registry & binds to a localhost address, to
which a user connects using their favourite browser. Except that
perhaps it could be too tedious for the user to run 2 programs in a
consecutive manner + Node itself isn't installed on Windows by
default.
32bit node.exe
binary is < 10MB zipped, so shipping it with the app
isn't a big deal. To simplify the program startup we could write a C
wrapper that starts the node server & opens the default browser,
except that there's no need to, for w10 still comes with Windows
Scripting Host! God almighty, WSH is awful: its JScript is a subset of
ES3 with interesting constructs like env("foo") = "bar"
(this is not
a joke) & virtually every WSH resource describes it using VBScript for
reasons unbeknown to the mankind. Nevertheless, for a tiny wrapper
it's an acceptable choice, especially if you manage not to puke while
reading the antediluvian VBScript examples.
Fonts
The values inside HKCU\Control Panel\Desktop\WindowMetrics
key that
correspond to font options, like MenuFont
, are binary blobs:
$ reg query "HKCU\Control Panel\Desktop\WindowMetrics" /v MenuFont
HKEY_CURRENT_USER\Control Panel\Desktop\WindowMetrics
MenuFont REG_BINARY F1FFFFFF0000000000000000000000009001000000000001000000005300650067006F006500200055004900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
People say the blobs are serialized Logfont
structures. Apparently, MS guys have decided it's a safe bet, for the
structure doesn't contain pointers.
Can we decode it properly in a browser using JS? I leave such a strive
to another enterprising soul. But we can write a tiny C program that
displays some 'standard' Windows font dialog. The idea is: a user
clicks on a button in the browser, the browser sends a request to the
node server.js
that runs my-font-dialog.exe
that shows the font
dialog & prints to the stdout a deciphered Logfont
structure + the
hex encoded Logfont
. Then the server sends the obtained data to the
browser, that, in turn, updates the fake html controls.
It's not actually a logfont
If you don't know any winapi, how hard is to write
my-font-dialog.exe
? Can a non-console Windows program write to the
stdout? Or should I use some kind of IPC if it cannot? The more I
thought about downloading the Windows SDK the more I regarded the
whole endeavour with aversion.
Then it dawned on me that I can take a shortcut & utilise Cygwin! It
has w32api-runtime
& w32api-headers
packages, its C library has
all the friendly helpers like readline()
. Its 64bit build even has
32bit version of gcc (cygwin32-gcc-core
, cygwin32-binutils
, etc),
thus we can create on a 64bit machine a 32bit executable. E.g., having
a simple Makefile:
target := i686-pc-cygwin
CC := $(target)-gcc
LD := $(target)-ld
LDFLAGS := -mwindows -mconsole
& foo.c
file, typing
$ make foo
produces such a foo, that can invoke Windows gui routines &
simultaneously can access the stdin/stdout. The resulting exe depends
on a 32bit cygwin1.dll
but while we ship it with the app, nobody
cares.
To display an ancient Windows font dialog box, we ought to call
ChooseFont()
fn that fills a special CHOOSEFONT
structure. One
field of that structure is the desired Logfont
.
$ echo F4FFFFFF000000000000000000000000900100000000000100000500530065006700 6F006500200055004900000000000000000000000000000000000000000000000000000000000 000000000000000000000000000000000000000 | _out.i686-pc-cygwin/app/cgi-bin/choosefont.exe
If a user click OK, the choosefont.exe util prints:
F1FFFFFF00000000000000000000000090010000000000010302014249006E006B0020004600720065006500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000,Ink Free,400,0,11.250000,15
When I converted the Logfont
value into a hex string I noticed that
although it looked decidedly similar to the values in the registry, it
wasn't the same: the blob was shorter. It was either an obscure
Unicode issue with the toolchain or I didn't know how to computer or
both. Have I mondegreened about the Logfont
? Turns out that not
everything you read on the web is accurate: it's not Logfont
, it's
Logfontw
! I don't see a point of describing the reason why or what
macro you should define to get Anythingw automatically, for
Windows programmers are already laughing at me. Evidently, this is
what you get for avoiding Visual Studio.
System logger
Being naïve, I thought of sending errors from server.js
to the
Windows event log. Ain't logs often useful? What is their syslog(3)
here? ReportEvent()
? Alrighty, we'll write a small C util to aid
server.js
!
facepalm.jpg
I was going to painstakingly describe the steps one must take to
obtain the permissions for the proper writing to the event log, but
ultimately deleted all the corresponding code & decided to stay with
console.error
. To get a sense for a gallant defence Windows makes
before permitting anyone to write to its hallowed log storage, read
these 2 SO answers:
Your pixels are not my pixels
At some point I wanted to show the current DPI to the user. There are
ways to do it in a browser without external help, but they are
unreliable in edge cases. Winapi has GetDpiForMonitor()
.
You think it's a piece of cake, until you play with w10 'custom
scaling' feature. For some reasons it allows only to specify a
relative value.
In
p = (d/c) * 100
p is the %-value in the Windows custom scaling input field, c is
the current dpi, d--the desired one.
Say your initial dpi is 96 & you want to upgrade to 120, then p = 125
= (120/96) * 100. For the desired 144 dpi, p = 150.
This all breaks if d fails to hit one of the 'standard' Windows DPIs
(120, 144). In such cases GetDpiForMonitor()
fn yields 96.
The result
Download the full, pre-compiled app from the github
page.
Tags: ойті
Authors: ag