๐พ Archived View for boringcactus.com โบ feed.xml captured on 2021-12-03 at 14:04:38.
โฌ ๏ธ Previous capture (2021-11-30)
-=-=-=-=-=-=-
<?xml version="1.0" encoding="utf-8"?> <feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en-US"> <link href="gemini://boringcactus.com/feed.xml" rel="self" type="application/atom+xml" /> <link href="gemini://boringcactus.com/" rel="alternate" type="text/gemini" /> <updated>2021-10-26T21:48:33.134323Z</updated> <id>gemini://boringcactus.com/feed.xml</id> <title>boringcactus</title> <subtitle>boringcactus's blog posts</subtitle> <author> <name>boringcactus / Melody Horn</name> <uri>/</uri> </author> <entry> <title>A 2021 Survey of Rust GUI Libraries</title> <link href="gemini://boringcactus.com/2021/10/24/2021-survey-of-rust-gui-libraries.gmi" rel="alternate" type="text/gemini" title="A 2021 Survey of Rust GUI Libraries" /> <published>2021-10-24T00:00:00Z</published> <updated>2021-10-24T00:00:00Z</updated> <id>gemini://boringcactus.com/2021/10/24/2021-survey-of-rust-gui-libraries.gmi</id> <content type="text/gemini">A year and a half ago I looked at a bunch of different Rust GUI libraries; that's a long time, so let's see if things have changed. => /2020/08/21/survey-of-rust-gui-libraries.gmi looked at a bunch of different Rust GUI libraries As before, some context:
โโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโ
โ library โ easy setup? โ nonzero โ overall vibe check โ
โ โ โ accessibility? โ โ
โโโโโโโโโโโโโโโโโโโโโโโชโโโโโโโโโโโโโโชโโโโโโโโโโโโโโโโโโโโโโชโโโโโโโโโโโโโโโโโโโโโก
โ Azul โ โ โ โ โ
โโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโค
โ Conrod โ โ โ โ โ
โโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโค
โ core-foundation โ โ โ โ โ
โโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโค
โ druid โ โ โ โ โ โ
โโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโค
โ egui โ โ โ โ โ โ
โโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโค
โ fltk โ โ โ โ โ โ
โโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโค
โ GTK โ โ โ โ โ
โโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโค
โ Iced โ โ โ โ โ โ
โโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโค
โ imgui โ โ โ โ โ
โโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโค
โ iui โ โ โ โ โ ๐ โ
โโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโค
โ KAS โ โ โ โ โ โ
โโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโค
โ lvgl โ โ โ โ โ
โโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโค
โ native-windows-gui โ โ โ โ โ ๐คท โ
โโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโค
โ OrbTk โ โ โ โ โ โ
โโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโค
โ qmetaobject-rs โ โ โ โ โ
โโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโค
โ qt_widgets โ โ โ โ โ
โโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโค
โ relm โ โ โ โ โ
โโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโค
โ rust-qt-binding- โ โ โ โ โ
โ generator โ โ โ โ
โโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโค
โ sciter-rs โ โ โ โ โ
โโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโค
โ SixtyFPS โ โ โ โ โ โ
โโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโค
โ tauri โ โ โ โ โ
โโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโค
โ vgtk โ โ โ โ โ
โโโโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโโ
## Azul The installation guide says you need to separately download the precompiled library and make sure it's in the right folder and ughhhh that's more setup than adding it to `Cargo.toml` and i literally put in my rules that that's more work than i want to do. Rust already has a build system, I don't want to have to fuck with things independently of that. => https://azul.rs/ Azul => https://azul.rs/guide/1.0.0-alpha1/Installation installation guide ## Conrod My previous adventures in Rust GUI development have led me to Conrod several times, and most of those times it's been a hassle, but we'll see if things have changed since then. The hello world tutorial appears to actually say which backends to use, which is a welcome and sorely-needed improvement. => https://github.com/pistondevelopers/conrod Conrod => https://docs.rs/conrod_core/0.75.0/conrod_core/guide/chapter_3/index.html hello world tutorial Wait, what did that say there? > At the time of writing (September 2017), Ok hold up, how outdated is this? It said to depend on `conrod 0.55.0`, but it looks like the latest version is actually `0.75.0`. And there is no `conrod 0.75.0`, apparently, because they split the crate into several different crates as of 0.62.0; allegedly I am "most likely looking for `conrod_core`." So the guide, which is *part of `conrod_core`*, is so out of date that it uses the wrong name for itself. If I just use `conrod_core 0.75.0` where the guide said to use `conrod 0.55.0` my issues have not ceased, though: => https://crates.io/crates/conrod allegedly
error: failed to select a version for `conrod_core`.
... required by package `conrod-demo v0.1.0 (D:\Melody\Projects\misc\gui-survey-2021\conrod-demo)`
versions that meet the requirements `^0.75.0` are: 0.75.0
the package `conrod-demo` depends on `conrod_core`, with features: `glium` but `conrod_core` does not have these features.
So the officially-recommended backends don't exist anymore, or at least they no longer exist in the same way and it's my job to figure out how to get them back. I don't want to debug the official tutorial that's linked in the README. As such, Conrod appears to not be for me. ## core-foundation "Bindings to Core Foundation for macOS" are cool if I'm using macOS. As established, I'm not. => https://github.com/servo/core-foundation-rs core-foundation ## druid Previously, druid was one of the two libraries I was most impressed by; let's see if that still holds. The "this is outdated, and should be replaced with a walkthrough of getting a simple app built and running" message in the official guide persists, and upon further inspection there's been one release since my last blog post, with few new features. => https://linebender.org/druid/ druid => https://linebender.org/druid/get_started.html the official guide The "hello world" code in the guide has broken - something that used to take a value now takes a closure returning that value - but that's an easy fix. A harder fix, though, is that in the official "hello world" example, Windows Narrator can't find the text "hello world". In my last post, I treated accessibility as an afterthought, to the point where I had to go in several months later and retroactively check for and add information about screen reader support; this was the wrong decision for me, and it's the wrong decision for druid. They list "Offer robust accessibility support" under their goals; maybe one day they'll get there. ## egui This is the first entry on Are We GUI Yet that wasn't there last year, and it was seeing something about this on Twitter that got me interested in revisiting this topic in the first place. There's an official template that comes with some (bash, and thereby more-painful-on-Windows, but what can you do) scripts for managing WASM builds, but I'm not interested in WASM at this point, so I'll just yoink the code and undo the multi-track drifting that allows WASM builds (which they probably don't actually need to do because wasm-bindgen can just do the right thing on binary crates due to work that i did almost exactly two years ago now, but whatever). => https://github.com/emilk/egui egui => https://github.com/rustwasm/wasm-bindgen/pull/1843 probably don't actually need to do because wasm-bindgen can just do the right thing on binary crates due to work that i did almost exactly two years ago now It runs correctly, which is good, but out of the box, Windows Narrator can't tell what's going on. There is, however, work-in-progress screen reading support which we can opt into with the `screen_reader` feature. Unfortunately, this appears not to do anything, at least for me with my current setup and near-zero knowledge of egui. Hopefully this becomes more robust soon. => https://github.com/emilk/egui/issues/167 work-in-progress screen reading support ## FLTK The presence of the `fltk-bundled` feature is very much appreciated. Unfortunately, it doesn't make up for a lack of screen reader accessibility. => https://github.com/fltk-rs/fltk-rs FLTK ## GTK The GTK project appears to be putting a lot of effort towards providing good Rust bindings. Unfortunately, => https://gtk-rs.org/ GTK
warning: Could not run `"pkg-config" "--libs" "--cflags" "glib-2.0" "glib-2.0 >= 2.48"`
Full disclosure: I have tried several times to get GTK 3 or 4 working for development on this computer. I have failed several times, and succeeded none. I am disinclined to try and fail again. ## Iced Iced was one of the two projects I was impressed by last time around. Unfortunately, that was before I started caring about accessibility as much; the counter example, when ran as a native binary, gives no information to Narrator. => https://github.com/iced-rs/iced Iced ## imgui The README doesn't have a self-contained example, but there's a hello world example that looks pretty reasonable. But that `mod support;` declaration looks like it's probably abstracting out some complexity, let me justโoh god my eyes that's a lot of boilerplate. Am I supposed to copy and paste all that logic into my code? If so, why is it not just part of the library already? If not, why do all the examples have it? => https://github.com/imgui-rs/imgui-rs imgui => https://github.com/imgui-rs/imgui-rs/blob/v0.8.0/imgui-examples/examples/hello_world.rs hello world example => https://github.com/imgui-rs/imgui-rs/blob/v0.8.0/imgui-examples/examples/support/mod.rs oh god my eyes As stated, I don't already have a graphics pipeline set up, so I think I am not the target audience for imgui. ## iui This one's also a new addition compared to my previous writeup. And I'll be damned, it actually works! Didn't have to mess around with compilers, worked just fine with Narrator. => https://github.com/rust-native-ui/libui-rs iui It's been three years since the last crates.io release, but I'm writing this in the same order you're reading it, so this is the first one I've seen that actually has any accessibility support whatsoever. If more than a handful of other libraries can clear that not-very-high bar, I'll fill in a more robust comparison, but for now this is pretty damn good. ## KAS Runs fine, doesn't give Narrator any info. => https://github.com/kas-gui/kas KAS ## lvgl Setting up `DEP_LV_CONFIG_PATH` is 1. more work than just adding it to `Cargo.toml` and 2. apparently some bullshit based on C header configuration?? => https://github.com/rafaelcaricio/lvgl-rs lvgl ## native-windows-gui Unsurprisingly, this works like a charm on Windows. No installation problems, no accessibility problems, no worries. It does, of course, only work on Windows, though, and I would love to wind up with code that theoretically should be cross-platform. => https://github.com/gabdube/native-windows-gui native-windows-gui ## OrbTk Works, no accessibility. => https://github.com/redox-os/orbtk OrbTk ## qmetaobject-rs
Error: Failed to execute qmake. Make sure 'qmake' is in your path!
## qt_widgets
failed to run command: "qmake" "-query" "QT_VERSION"
## relm
LINK : fatal error LNK1181: cannot open input file 'gtk-3.lib'
## rust-qt-binding-generator installing Qt is more work than i want to have to do. => https://github.com/woboq/qmetaobject-rs qmetaobject-rs => https://rust-qt.github.io/ qt_widgets => https://github.com/antoyo/relm relm => https://invent.kde.org/sdk/rust-qt-binding-generator rust-qt-binding-generator ## sciter-rs downloading the SDK independent of Cargo is more work than i want to have to do. => https://github.com/sciter-sdk/rust-sciter sciter-rs ## SixtyFPS works, no accessibility => https://sixtyfps.io/ SixtyFPS ## Tauri whatever the hell any of this is is more work than i want to have to do. it looks like this wants you to have both npm/yarn and Cargo? and i don't think i like the implications of that. => https://tauri.studio/en/ Tauri => https://tauri.studio/en/docs/usage/development/integration#1-start-a-new-tauri-project whatever the hell any of this is ## vgtk
Could not run `"pkg-config" "--libs" "--cflags" "glib-2.0" "glib-2.0 >= 2.44"`
## WebRender what the goddamn hell is this supposed to be? => https://github.com/bodil/vgtk vgtk => https://github.com/servo/webrender WebRender => https://github.com/servo/webrender/blob/master/examples/common/boilerplate.rs what the goddamn hell is this supposed to be the basic example is doing shader fuckery! that doesn't seem basic to me! ## summary Well, shit. Turns out if you want low-fuss Windows setup, nonzero accessibility, and theoretical cross-platform support, and you want them in the present rather than the future, there's only one option (at least among the things listed on Are We GUI Yet). Shout out to iui, in all its three-year-old-latest-release no-way-to-draw-images(-at-least-that-I-can-find) glory. Honorable mention to egui for having put in at least some effort towards accessibility; if I revisit this topic in another year or two, I suspect I'll be very impressed with egui. Additional reference to SixtyFPS who have made an entire other markup language for specifying UI structure; I'm not sure it's a good or necessary approach, but it's eye-catching, and maybe at some point they'll start working on accessibility and I'll take a more thorough look. (I'm not entirely sure the extent to which German and/or EU law requires commercial products meet accessibility standards, but if SixtyFPS is doing commercial licensing, they'd better have looked into that.) => https://github.com/rust-native-ui/libui-rs iui => https://github.com/emilk/egui egui => https://sixtyfps.io/ SixtyFPS ## whining I've worked with Python and Rust a lot, and those are my two languages of choice for new personal projects these days. Python, as an interpreted language, does not really go out of its way to give you binaries that you can just hand to end users; it's possible, but as of the last time I checked, the tools were all varying shades of pains in the ass to use. Rust seems like it'd be far better suited for end-user application development, since at the end you have a .exe or whatever that you can hand off to anybody. It looks like the Rust ecosystem doesn't really have a solid foundation for GUI development, despite being a good language for GUI development. Python is set up such that it's easier to write tools for other developers (who are likely to have a Python interpreter installed) than for end users (who are not), and developers tend to prefer CLI programs to GUI ones, so it would be reasonable to expect GUI tools in Python to be worse than they are in Rust, right? Let's check in on that:
import wx
app = wx.App()
frame = wx.Frame(None, title="wxPython Example")
panel = wx.Panel(frame)
text = wx.StaticText(panel, label="Hello World!")
frame.Show()
app.MainLoop()
This was easy to set up on Windows, correctly provides accessibility data to Windows Narrator, and was really concise to implement. So if I want a nice, powerful, elegant GUI library without losing my mind trying to set it up, I can have that, as long as I don't need other people to be able to conveniently run the program I make. How the goddamn fuck did we get here? Unfortunately, I know the answer to that one. wxWidgets, the library wxPython is binding to, is a C++ library that is used primarily by inheriting from wxWidgets's classes. Rust bindings to C++ libraries are difficult to create in the best of scenarios, and inheritance is among the worst of scenarios, because Rust does not have any concepts to which C++ inheritance maps cleanly. The wxPython bindings are maintained by the wxWidgets team themselves, and Python is designed to be extended by C/C++ code. Rust's FFI with C is fine, but bindgen is really bad at handling C++ as used by wxWidgets, and manually authoring the thousands of bindings and then providing a safe abstraction over them would be extraordinarily not a good time. So if wxWidgets isn't feasible to use from Rust, what about some other library? Well, the only reason wxPython is as easy to install as it is is that the maintainers made sure it comes with the fully-compiled wxWidgets binaries, especially on Windows where everything of that sort is harder to do by hand after the fact. Some Rust bindings to C libraries, like SDL2 or the aforementioned FLTK, will build the libraries they depend on automatically, encapsulating all that complexity and letting me just use them; most, like GTK, do not. I'm not sure if there's a technical reason gtk-rs and the rest of these can't do that (or, for bonus points, just download a precompiled binary from the official releases and save a bunch of time), or if it's just that nobody's asked for it. Linux (and to some extent Mac I suppose) devs are accustomed to niceties like `pkg-config` and actual package managers that make it easy to install a C library; on Windows, we don't have that (unless we're working inside MSYS2 or what have you, but that's its own mess sometimes). So please: if you're writing Rust bindings to a C library, do your Windows users a favor, and give us a `bundled` feature.</content> </entry> <entry> <title>An Anti-License Manifesto</title> <link href="gemini://boringcactus.com/2021/09/29/anti-license-manifesto.gmi" rel="alternate" type="text/gemini" title="An Anti-License Manifesto" /> <published>2021-09-29T00:00:00Z</published> <updated>2021-09-29T00:00:00Z</updated> <id>gemini://boringcactus.com/2021/09/29/anti-license-manifesto.gmi</id> <content type="text/gemini">software licenses are unavoidably a legal tool. the legal system, in the US and approximately everywhere else, is not a machine that leads to justice. therefore, software licenses do not lead to justice. we cannot software license our way to a better world. as such, we should and must software license our way to a *stranger* world. permissive licenses and copyleft licenses are both tools of the corporate status quo. we therefore reject all conventional software licenses, and instead champion the weird, the experimental, the decorative, the hostile, the absurd, the useless, the straight up unhinged. some anti-licenses:
PS D:\Melody\Projects\boringcactus.com> git fast-export --all | fossil import --git D:\Melody\Projects\misc\boringcactus.com.fossil
]ad fast-import line: [S IN THE
Well, then. You can't be better than Git if your instructions for importing from Git don't actually work. (Although you can totally be better than Git if you can keep track of issues etc. alongside the code.) ## Darcs Darcs is a distributed VCS that's a little different to Git etc. Git etc. have the *commit* as the fundamental unit on which all else is built, whereas Darcs has the *patch* as its fundamental unit. This means that a branch in Darcs refers to a set of patches, not a commit. As such, Darcs can be more flexible with its history than Git can: a Git commit depends on its temporal ancestor ("parent"), whereas a Darcs patch depends only on its logical ancestor (e.g. creating a file before adding text to it). This approach also improves the way that some types of merge are handled; I'm not sure how often this sort of thing actually comes up, but the fact that it could is definitely suboptimal. => http://darcs.net/ Darcs => https://tahoe-lafs.org/~zooko/badmerge/simple.html some types of merge So that's pretty cool; let's take a look for ourselves. Oh. Well, then. The download page is only served over plain HTTP - there's just nothing listening on that server over HTTPS - and the downloaded binaries are also served over plain HTTP. That's not a good idea. I'll pass, thanks. => http://darcs.net/Binaries download page You can't be better than Git while serving binaries over plain HTTP. (Although you can totally be better than Git by having nonlinear history and doing interesting things with patches.) ## Pijul Pijul is (per the manual) => https://pijul.org/ Pijul => https://pijul.org/manual/why_pijul.html the manual > the first distributed version control system to be based on a sound mathematical theory of changes. It is inspired by Darcs, but aims at solving the soundness and performance issues of Darcs. Inspired by Darcs but better, you say? You have my attention. Also of note is that the developers are also building their own GitHub clone, which they use to host pijul itself, which gives a really nice view of how a GitHub clone built on top of pijul would work, and also offers free hosting. => https://nest.pijul.com/pijul/pijul host pijul itself The manual gives installation instructions for a couple Linuces and OS X, but not Windows, and not Alpine Linux, which is the only WSL distro I have installed. However, someone involved in the project showed up in my mentions to say that it works on Windows, so we'll just follow the generic instructions and see what happens: => https://pijul.org/manual/installing.html installation instructions => https://twitter.com/nuempe/status/1359614145415548939 showed up in my mentions to say that it works on Windows
PS D:\Melody\Projects> cargo install pijul --version "~1.0.0-alpha"
Updating crates.io index
Installing pijul v1.0.0-alpha.38
Downloaded <a bunch of stuff>
Compiling <a bunch of stuff>
error: linking with `link.exe` failed: exit code: 1181
|
= note: "C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\BuildTools\\VC\\Tools\\MSVC\\14.27.29110\\bin\\HostX64\\x64\\link.exe" <lots of bullshit>
= note: LINK : fatal error LNK1181: cannot open input file 'zstd.lib'
error: aborting due to previous error
So it doesn't work for me on Windows. (There's a chance that instructions would help, but in the absence of those, I will simply give up.) Let's try it over on Linux:
UberPC-V3:~$ cargo install pijul --version "~1.0.0-alpha"
<lots of output>
error: linking with `cc` failed: exit code: 1
|
= note: "cc" <a mountain of arguments>
= note: /usr/lib/gcc/x86_64-alpine-linux-musl/9.3.0/../../../../x86_64-alpine-linux-musl/bin/ld: cannot find -lzstd
/usr/lib/gcc/x86_64-alpine-linux-musl/9.3.0/../../../../x86_64-alpine-linux-musl/bin/ld: cannot find -lxxhash
collect2: error: ld returned 1 exit status
error: aborting due to previous error
UberPC-V3:~$ sudo apk add zstd-dev xxhash-dev
UberPC-V3:~$ cargo install pijul --version "~1.0.0-alpha"
<lots of output again because cargo install forgets dependencies immediately smdh>
Installed package `pijul v1.0.0-alpha.38` (executable `pijul`)
Oh hey, would you look at that, it actually worked, and all I had to do was wait six months for each compile to finish (and make an educated guess about what packages to install). So for the sake of giving back, let's add those instructions to the manual, so nobody else has to bang their head against the wall like I'd done the past few times I tried to get Pijul working for myself. First, clone the repository for the manual: => https://nest.pijul.com/pijul/manual repository for the manual
UberPC-V3:~$ pijul clone https://nest.pijul.com/pijul/manual
Segmentation fault
Oh my god. That's extremely funny. Oh fuck that's *hilarious* - I sent that to a friend and her reaction reminded me that Pijul is *written in Rust*. This VCS so profoundly doesn't work on my machine that it manages to segfault in a language that's supposed to make segfaults impossible. Presumably the segfault came from C code FFId with `unsafe` preconditions that weren't met, but still, that's just amazing.
PS D:\Melody\Projects\misc> pijul clone https://nest.pijul.com/pijul/manual pijul-manual
โ Updating remote changelist
โ Applying changes 47/47
โ Downloading changes 47/47
โ Outputting repository
Hey, that actually works! We can throw in some text to the installation page (and more text to the getting started page) and then use `pijul record` to commit our changes. That pulls up *Notepad* as the default text editor, which fails to spark joy, but that's a papercut that's entirely understandable for alpha software not primarily developed on this OS. Instead of having "issues" and "pull requests" as two disjoint things, the Pijul Nest lets you add changes to any discussion, which I very much like. Once we've recorded our change and made a discussion on the repository, we can `pijul push boringcactus@nest.pijul.com:pijul/manual --to-channel :34` and it'll attach the change we just made to discussion #34. (It appears to be having trouble finding my SSH keys or persisting known SSH hosts, which means I have to re-accept the fingerprint and re-enter my Nest password every time, but that's not the end of the world.) => https://nest.pijul.com/pijul/manual/discussions/34 discussion #34 So yeah, Pijul definitely still isn't production-ready, but it shows some real promise. That said, you can't be better than Git if you aren't production-ready. (Although you can totally be better than Git by having your own officially-blessed GitHub clone sorted out already.) (And maybe, with time, you can be eventually better than Git.) ## what next? None of the existing VCSes that I looked at were unreservedly better than Git, but they all had aspects that would help beat Git. A tool which is actually better than Git should start by being no worse than Git:
Type โ 'const' BasicType /
BasicType '*' /
BasicType '[' Expression ']' /
BasicType 'function' '(' (BasicType ',')* ')' /
BasicType
BasicType โ 'void' /
'int' /
'float' /
'bool' /
'(' Type ')'
So essentially, basic types can be used as-is, and pointers-to or arrays-of those basic types require no additional syntax. But if you want to do something nontrivial, you'll need to parenthesize the inner type. I didn't think this would wind up being quite as elegant as it turned out to be, but it handles a lot of edge cases gracefully and intuitively. ## In Motion I'll just lift some examples straight from the Spiral Rule page.
char *str[10];
Evidently this means "str is an array 10 of pointers to char". How would we express that in Crowbar (as it hypothetically exists so far)?
(char *)[10] str;
Now that's more like it. We can look at it and tell right away that the array is the outermost piece and so `str` is an array. In C, I'm not sure how we'd express a pointer-to-arrays-of-10-chars, but in Crowbar it's also straightforward:
(char[10])* str;
Now let's kick it up a notch, and look at those legendarily-awful aspects of C's syntax, function pointers. The Spiral Rule offers up
char *(*fp)( int, float *);
which supposedly means "fp is a pointer to a function passing an int and a pointer to float returning a pointer to a char". That's not extremely dreadful, merely somewhat off-putting, but let's see how it looks in Crowbar.
((char *) function(int, (float *),)* fp;
I hate that way less. It's less terse, certainly, but it's more explicit. The variable name is where it belongs, instead of nestled three layers deep inside the declaration. The fact that the `char *` and `float *` need to be parenthesized here is probably unnecessary, but you could imagine situations where those parentheses would be vital. And introducing `function` as a keyword means you can look at it and know instantly that it's a pointer-to-a-function, instead of going "wait what's that syntax where there are more parentheses than you'd think you'd want? oh yeah it's function pointers." So let's take a look at the worst thing C can offer. The Spiral Rule calls it the "ultimate", and I don't think that's a misnomer:
void (*signal(int, void (*fp)(int)))(int);
That fractal mess is "a function passing an int and a pointer to a function passing an int returning nothing (void) returning a pointer to a function passing an int returning nothing (void)". My eyes glaze over reading that description even more than they do reading the original C. Can we make this not look awful?
((void function(int,))*) signal(int, ((void function(int,))*),);
This is beautiful. (Well, no it isn't, but it's way less ugly than the original.) It's clear which things are functions and which things are not, the nesting is all transparent and visible, and *you can tell what the return type is without a PhD in Deciphering C Declarations*. Plus, importantly, it's clear that this is a function prototype and not a function pointer declaration, which is a massive improvement over the original. ## Bonus Round Just for kicks, another less-awful-but-still-not-great thing about C type syntax is the pointer-to-constant vs constant-pointer dichotomy.
const int * points_to_const; // can never do *points_to_const = 8;
int * const const_pointer; // can never do const_pointer = &x;
You have to remember which is which. And why memorize when you can read?
(const int)* points_to_const;
const (int *) const_pointer;
Much, much better. ## Looking Forwards This syntax is simpler than C's without losing any expressive power. That makes me very happy. If you're curious what's coming next for Crowbar, watch this space for when I eventually write another Crowbar-related blog post, or join the mailing list. (But don't get your hopes up; Crowbar is a project I'm working on in my spare time on a when-I-feel-like-it basis.) => https://sr.ht/~boringcactus/crowbar-lang/lists join the mailing list</content> </entry> <entry> <title>Crowbar: Defining a good C replacement</title> <link href="gemini://boringcactus.com/2020/09/28/crowbar-1-defining-a-c-replacement.gmi" rel="alternate" type="text/gemini" title="Crowbar: Defining a good C replacement" /> <published>2020-09-28T00:00:00Z</published> <updated>2020-09-28T00:00:00Z</updated> <id>gemini://boringcactus.com/2020/09/28/crowbar-1-defining-a-c-replacement.gmi</id> <content type="text/gemini">I like Rust a lot. That said, the always-opinionated, ~~often-correct~~ occasionally-correct Drew DeVault raises some good points in his blog post Rust is not a good C replacement. He names some attributes that C has and Rust lacks which he thinks are required in a good C replacement. So what can we say are some features of a hypothetical good C replacement? => https://web.archive.org/web/20201020022457if_/https://cmpwn.com/@sir/104946211496582150 occasionally-correct => https://drewdevault.com/2019/03/25/Rust-is-not-a-good-C-replacement.html Rust is not a good C replacement ## Portability Per Drew, "C is the most portable programming language." Rust, via LLVM, supports a lot of target architectures, but C remains ubiquitous. Anything less portable than LLVM is, therefore, incapable of being a good C replacement, and to be a truly great C replacement a language should be as portable as C. The lifehack that makes sense here to me, difficult as it might be to implement, is to have the compiler for your C replacement be able to emit C as a compile target. It might not be the same C that a competent C programmer would write by hand, the result might not initially be as fast as the compiler would be if it were running the full toolchain directly, but in principle a self-hosting compiler that could also emit C would be extremely easy to bootstrap on any new architecture. ## Specification The C specification, while large and unwieldy and expensive, does at least exist. This creates a clear definition for how a compiler *should* work, whereas the behavior of Rust-the-language is defined to be whatever rustc-the-compiler does. Rust's lack of a specification allows it to move fast and not break things, which is cool for Rust (shout out to async/await), but there's virtue in moving slow, too. I don't think Rust itself necessarily needs a specification, but I do think that a good C replacement should be formally specified, and move at the speed of bureaucracy to ensure only the best changes get actually made. The C99 specification is 538 pages and costs more money than I'd want to spend to get a legitimate copy of. A good C replacement should probably have a specification that fits in half the size - hell, call it 200 pages for a nice round number - and that specification should be released free of charge and under a permissive license. ## Diverse Implementations I think this is a result of having a specification more than a cause of its own, but Drew points out that the various C compilers keep each other and the spec honest and well-tested. I think in principle a good C replacement could start out with only one compiler, but it would need to be fairly straightforward to write a new compiler for - which is certainly not the case for C. It might be helpful to have several different reference compilers, themselves written in different languages and for different contexts. (It would be a fun party trick to have a WebAssembly-based compiler that people could poke around at in their browsers.) ## Zero-Effort FFI Drew calls this point "a consistent and stable ABI," but I think the context in which that matters is being able to use other libraries from your code, and being able to compile your code as a library that other people can use in turn, without needing to jump through extra hoops like writing glue layers manually or recompiling everything from source. The easy ("easy") solution is to just build libraries that C code can just use directly, and use the same ABI that C libraries in turn would export, so that there's no such thing as FFI because no functions exist which are foreign. ## Composable Build Tools I don't give a shit about this one, but in fairness to Drew I could understand why he would. If I have a project that starts out being entirely C, and I'm like "oh lemme try out Rust for some of this", I don't want to start by swapping out my build system and everything, I want to keep my Makefiles and make like one change to them and call it a day. (Makefiles also suck, but they don't dual-wielding-footguns suck, so it doesn't matter.) But cargo is very decidedly not built to be used that way, and rustc is very decidedly not built to be called from things that aren't cargo. So a good C replacement should have a compiler that works the way C compilers tend to, i.e. you pass in a file or several and a bunch of arguments and it builds the file you need it to build. ## *fart noises* > Concurrency is generally a bad thing. ## Safety Is Just Another Feature In light of all the problems he has with Rust, Drew declares > Yes, Rust is more safe. I donโt really care. In light of all of these problems, Iโll take my segfaults and buffer overflows. And like. On one hand, those are all extremely avoidable problems, and the way to avoid them is to start using a memory safe language. And they do cause *rather more substantial problems* than not having a specification. But also. On the other hand, I don't eat my vegetables, I only comment my code half the time, I use Windows and I don't hate it. We're all of us creatures of habit, we do what's familiar, and we'll keep doing things in dangerous ways if switching to safe ways would have too high a learning curve. So as much of a bruh moment as this is in the abstract, I've been there, I'll likely be there again, and I can't really judge all that harshly. ## Crowbar The conclusion to Drew's blog post opens like this: > C is far from the perfect language - it has many flaws. > However, its replacement will be simpler - not more complex. And those sentences have been rattling around in my brain for a while now. Because he's right about that much. If we want C programmers to stop using C, we have to give them something that's only as different from C as it actually needs to be. We need C: The Good Parts Plus Barely Enough To Add Memory Safety And Good Error Handling. And as someone with extraordinarily poor time management skills, I think I kinda want to work on building that. Naming things is widely known to be the hardest problem in computer science, but since the goal is to pry C diehards away from C, I think a good name for this project would be Crowbar: the good parts of C, with a little bit extra. => https://sr.ht/~boringcactus/crowbar-lang/ Crowbar: the good parts of C, with a little bit extra I don't know if this will get any of my time and attention. It really shouldn't - I've got so much other shit I should be doing instead - but also this is fun so who knows. Join the mailing lists if you're interested in participating.</content> </entry> <entry> <title>A Survey of Rust Embeddable Scripting Languages</title> <link href="gemini://boringcactus.com/2020/09/16/survey-of-rust-embeddable-scripting-languages.gmi" rel="alternate" type="text/gemini" title="A Survey of Rust Embeddable Scripting Languages" /> <published>2020-09-16T00:00:00Z</published> <updated>2020-09-16T00:00:00Z</updated> <id>gemini://boringcactus.com/2020/09/16/survey-of-rust-embeddable-scripting-languages.gmi</id> <content type="text/gemini">Rust is a nice programming language. But it's only ever compiled, so if you want behavior that can be edited at runtime, you have to either anticipate and implement every single knob you think might need adjusting or let users write code in some other language that you then call from Rust. Lua is (to my knowledge) the most common language for this use case in general, but I don't really like using Lua all that much, so let's take a more comprehensive look at some of our options. We'll need a use case in mind before we can really get going here, so here's our motivating example. We've got an event enum that our host can send to our script:
pub enum Event {
Number(i64),
Word(String),
}
And we've got some operation that makes sense in the context of our host, which the script can call:
pub fn print_fancy(text: &str) {
println!("โจ{}โจ", text);
}
It'd be nice to reuse most of this structure, so let's define a consistent interface:
pub trait ScriptHost {
type ScriptContext;
type Error;
fn new_context() -> Result<Self::ScriptContext, Self::Error>;
fn handle_event(context: &mut Self::ScriptContext, event: Event) -> Result<(), Self::Error>;
}
And it'd be nice if almost all the logic was generic over `ScriptHost` so our individual examples could just call it as is:
pub fn run_main<SH: ScriptHost>() -> Result<(), SH::Error> {
let stdin = stdin();
let mut context = SH::new_context()?;
loop {
println!("Type a number or some text to fire an event, or nothing to exit.");
let mut input = String::new();
stdin.read_line(&mut input).unwrap();
let input = input.trim();
if input.is_empty() {
break;
}
let event = match input.parse() {
Ok(i) => Event::Number(i),
Err(_) => Event::Text(input.to_string())
};
SH::handle_event(&mut context, event)?;
}
Ok(())
}
And specifically, where possible, we'd like to have the script simply export a function we can call and pass in an event. That way, the script can maintain its own long-running state if it wants to. So that's the context in which we are working. If your context is not like this, then some libraries that don't work here might work for you. (If you're curious, you can check out the full source code for these examples here.) => https://git.sr.ht/~boringcactus/rust-scripting-languages here Our contenders will be pulled from the top crates.io results for the tags "script", "scripting", and "lua", sorted by Recent Downloads as of when I'm starting this blog post and with a cutoff of whenever I feel like stopping. Specifically (these are links to the sections where I discuss them, if you've got one in mind you're curious about) we have
struct LuaScriptHost;
impl ScriptHost for LuaScriptHost {
type ScriptContext = Lua;
type Error = Error;
When we make a new context, we want to create a binding to our `print_fancy` method. Aside from having to use a `String` instead of `&str` it's pretty straightforward:
fn new_context() -> Result<Lua, Error> {
let lua = Lua::new();
lua.context::<_, Result<(), Error>>(|lua_ctx| {
let print_fancy = lua_ctx.create_function(|_, text: String| {
print_fancy(&text);
Ok(())
})?;
lua_ctx.globals()
.set("print_fancy", print_fancy)?;
lua_ctx
.load(&(read_to_string("scripts/rlua-sample.lua").unwrap()))
.set_name("rlua-sample.lua")?
.exec()?;
Ok(())
})?;
Ok(lua)
}
(We're hard coding the path to the script here, because autodiscovering those is beyond the scope of this blog post, but you could totally iterate a directory and either run all those scripts in the same context or give them each their own context.) We're going to need a little more glue on the `Event` enum, though since Lua doesn't exactly have anything like enums natively. (And that glue is more complicated if we're using Cargo examples, because they aren't technically the same crate, so Rust's orphan rules kick in.) The easiest way to do that, I think, is to just add `get_number` and `get_text` methods that will return `nil` if the `Event` wasn't of that type:
struct EventForLua(Event);
impl UserData for EventForLua {
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_method("get_number", |ctx, this, _: ()| {
match this.0 {
Event::Number(number) => Ok(number.to_lua(ctx)?),
_ => Ok(Nil)
}
});
methods.add_method("get_text", |ctx, this, _: ()| {
match &this.0 {
Event::Text(text) => Ok(text.clone().to_lua(ctx)?),
_ => Ok(Nil)
}
});
}
}
Armed with all these tools, we can now actually write our Lua script:
function handle_event(event)
local number = event:get_number()
if number ~= nil then
print("number!", number)
end
local text = event:get_text()
if text ~= nil then
print("text!", text)
end
print_fancy("got an event!")
end
And now that we have our function, since we already ran this script, to handle an event all we need to do is call that function:
fn handle_event(context: &mut Lua, event: Event) -> Result<(), Error> {
context.context::<_, Result<(), Error>>(|lua_ctx| {
let handle_event: Function = lua_ctx.globals().get("handle_event")?;
handle_event.call::<_, ()>(EventForLua(event))?;
Ok(())
})?;
Ok(())
}
}
And that's all there is to it! That was surprisingly painless, all things considered. The only part that hurt a bit was the glue from `Event` into Lua, which wasn't even that bad for my case, but depending on what you're doing it might be. ## duckscript So Lua is a language I know I don't like all that much, and the gnarliest part of using it here was getting Rust types into it. Let's see if Duckscript, a "simple, extendable and embeddable scripting language", will be even simpler. => https://crates.io/crates/duckscript Duckscript Well, this is a minor nitpick, but if I want to write a custom command (which, I think, is the right way to get `print_fancy` available to our script) the trait for that requires that I manually write
fn clone_and_box(&self) -> Box<dyn Command> {
Box::new(self.clone())
}
even though *anything that is `Clone` can already be cloned into a box*. So that's a (very) minor nuisance. The less-minor nuisance, it turns out, is that I can't figure out how to actually run a function defined in a script. So I guess in Duckscript you can't do that, and we'll have to just rerun the whole script when the event gets fired. But once we've discovered and adapted to those issues, there's not much actual code that needs writing. Conveniently, since we can't have any persistent state anyway, there's no script context to store:
struct DuckscriptHost;
impl ScriptHost for DuckscriptHost {
type ScriptContext = ();
type Error = ScriptError;
fn new_context() -> Result<Self::ScriptContext, ScriptError> {
Ok(())
}
The glue to expose our `print_fancy` is pretty terse as well:
struct FancyPrintCommand;
impl Command for FancyPrintCommand {
fn name(&self) -> String {
"fancy_print".to_string()
}
fn clone_and_box(&self) -> Box<dyn Command> {
Box::new(self.clone())
}
fn run(&self, arguments: Vec<String>) -> CommandResult {
print_fancy(&arguments.join(" "));
CommandResult::Continue(None)
}
}
Once we've got this, we can handle events by setting up everything from scratch:
fn handle_event(_context: &mut Self::ScriptContext, event: Event) -> Result<(), ScriptError> {
// Make a context
let mut context = Context::new();
// Add the standard library
duckscriptsdk::load(&mut context.commands)?;
// Add our print_fancy command
context.commands.set(Box::new(PrintFancyCommand))?;
// Pick some strings to put in the context to represent our event
let (event_type, event_value) = match event {
Event::Number(value) => ("number", format!("{}", value)),
Event::Text(value) => ("text", value)
};
context.variables.insert("event.type".to_string(), event_type.to_string());
context.variables.insert("event.value".to_string(), event_value);
// Run the script
run_script_file("scripts/duckscript-sample.ds", context)?;
Ok(())
}
}
That's a lot of steps there. As you may notice, we have to put the number into a string before we can add it to the context. This is because all values in Duckscript are strings. Well, now that we've got all the code to pass in variables and run it, let's write our event handler script:
if eq ${event.type} "number"
echo "number!" ${event.value}
else
echo "text!" ${event.value}
end
print_fancy "Got an event"
Technically, this all works. But I don't like it. It might be enough for your use case, though, and if it is, it'll work. ## rhai We're still in search of a Rust-centric scripting language that's good for our use case, so let's see if Rhai, "an embedded scripting language and evaluation engine for Rust that gives a safe and easy way to add scripting to any application", will be that. It's got an entire documentation book, which is always nice to see, and WebAssembly support. So hopefully this one is as cool as it looks. => https://crates.io/crates/rhai Rhai => https://schungx.github.io/rhai/ an entire documentation book Page four of said documentation book gives some really helpful clarity: > Rhaiโs purpose is to provide a dynamic layer over Rust code, in the same spirit of zero cost abstractions. It doesnโt attempt to be a new language. That also sounds like exactly what we need. Just take a minute and *look at how fucking gorgeous this code is*:
struct RhaiScriptHost;
impl ScriptHost for RhaiScriptHost {
type ScriptContext = (Engine, Scope<'static>, AST);
type Error = Box<EvalAltResult>;
fn new_context() -> Result<Self::ScriptContext, Self::Error> {
let mut engine = Engine::new();
engine.register_fn("print_fancy", print_fancy);
let mut scope = Scope::new();
let ast = engine.compile_file_with_scope(&mut scope, PathBuf::from("scripts/rhai-sample.rhai"))?;
// if there's some global state or on-boot handling, make sure it runs
engine.consume_ast_with_scope(&mut scope, &ast)?;
Ok((engine, scope, ast))
}
fn handle_event((engine, scope, ast): &mut Self::ScriptContext, event: Event) -> Result<(), Self::Error> {
let argument = match event {
Event::Number(number) => Dynamic::from(number),
Event::Text(text) => Dynamic::from(text),
};
engine.call_fn(scope, ast, "handle_event", (argument,))?;
Ok(())
}
}
That's all of it. That's the whole damn thing. Our script is pretty darn straightforward, too, unsurprisingly:
fn handle_event(data) {
if type_of(data) == "i64" {
print("number! " + data);
} else {
print("text! " + data);
}
print_fancy("got an event!");
}
Rhai doesn't have pattern matching, so we can't use our `Event` enum directly, but it does have runtime type introspection, so we can simply pass in either a number or a string and let the script sort it out. (If this is possible in Lua, too, then we could've made our Lua implementation a little simpler, but I'm not as familiar with Lua.) There's so much to love here, but my favorite part is that we don't need any extra glue at all around `print_fancy` to be able to expose it to our Rhai script. This is *excellent*. Well done to Stephen Chung, Jonathan Turner, and the rest of the contributors. This is gonna be tough to beat. => https://github.com/jonathandturner/rhai/graphs/contributors the rest of the contributors ## dyon Ooh, dyon was built by one of the two big Rust game engine projects. (In fairness, though, rlua was built by the other one.) => https://crates.io/crates/dyon dyon On the upside, this is almost as straightforward as Rhai was:
struct DyonScriptHost;
impl ScriptHost for DyonScriptHost {
type ScriptContext = Arc<Module>;
type Error = Error;
fn new_context() -> Result<Self::ScriptContext, Error> {
let mut module = Module::new();
dyon_fn!{fn dyon_print_fancy(x: String) {
print_fancy(&x);
}}
module.add_str("print_fancy", dyon_print_fancy, Dfn::nl(vec![Type::Str], Type::Void));
load("scripts/dyon-sample.dyon", &mut module)?;
Ok(Arc::new(module))
}
fn handle_event(context: &mut Self::ScriptContext, event: Event) -> Result<(), Error> {
let call = Call::new("handle_event");
let call = match event {
Event::Number(value) => call.arg(value as f64),
Event::Text(value) => call.arg(value),
};
call.run(&mut Runtime::new(), context)?;
Ok(())
}
}
Our script is likewise pretty straightforward:
fn handle_event(data) {
if typeof(data) == "number" {
println("number! " + str(data))
} else {
println("text! " + data)
}
print_fancy("got an event!")
}
But the API took a really long time for me to actually figure out, and there are a few language features that I extremely don't get. Like, they've got secrets that can be attached to a bool or number, so you can ask for a retroactive explanation for why a function returned the value it did, or where a value came from. And that's neat and all, but like. why. Honestly I'm just not as impressed by this as I am by Rhai. Maybe you are, though, in which case this might work well for you. => http://www.piston.rs/dyon-tutorial/secrets.html secrets ## gluon gluon, unlike our previous contestants, is a statically typed functional language. Static types, for this context, are nice to have. Functional programming... may or may not be, depending on how it's implemented. => https://crates.io/crates/gluon gluon Oh hey, at time of writing their website is down. That's always a good sign. An even better sign, of course, is the fact that trying to declare our Event type in Gluon causes a stack overflow. In theory, since it's got algebraic data types, we could just declare it as is and bring it right on in, but unfortunately the code to do that 100% automatically appears to not compile, and the code to do that only almost automatically still requires us to write the type definition on the Gluon side manually:
vm.load_script("rust-scripting-languages.event", "type Event = Number Int | Text String")?;
I'm not sure why this causes a stack overflow either. But it does. As such, I have no clue how good this actually would be to use. ## ketos ketos describes itself as "a Lisp dialect functional programming language". So that ought to be interesting. => https://crates.io/crates/ketos ketos The Rust function glue all assumes your functions return a `Result` and that's a minor nuisance, but that's not insurmountable. Overall, this is pretty terse:
struct KetosScriptHost;
impl ScriptHost for KetosScriptHost {
type ScriptContext = Interpreter;
type Error = Error;
fn new_context() -> Result<Self::ScriptContext, Error> {
let interp = Interpreter::new();
let scope = interp.scope();
fn print_fancy_fallible(text: &str) -> Result<(), Error> {
print_fancy(text);
Ok(())
}
ketos_fn! { scope => "print-fancy" => fn print_fancy_fallible(text: &str) -> () }
interp.run_file(&PathBuf::from("scripts/ketos-sample.ket"))?;
Ok(interp)
}
fn handle_event(context: &mut Self::ScriptContext, event: Event) -> Result<(), Error> {
let value = match event {
Event::Number(n) => Value::from(n),
Event::Text(t) => Value::from(t),
};
context.call("handle-event", vec![value])?;
Ok(())
}
}
Our script itself is... definitely Lisp-y.
(define (handle-event event)
(do
(println "~a ~a" (type-of event) event)
(print-fancy "got an event!")))
This all works, and if you love Lisp and want your users to write a bunch of Lisp then this'll work perfectly for you. But it's not really to my tastes. ## rune rune lists pattern matching in their README as a feature, so that's a good sign. And they've got a Book that starts by giving a shout out to Rhai as a major inspiration, which is also neat. => https://crates.io/crates/rune rune Turns out the pattern matching doesn't let you match on enum variants even though Rune *has enums*. So we've gotta do the same dynamic typing stuff we've been doing before. But this is pretty clean:
struct RuneScriptHost;
impl ScriptHost for RuneScriptHost {
type ScriptContext = Vm;
type Error = Box<dyn Error + 'static>;
fn new_context() -> Result<Vm, Self::Error> {
let mut context = rune::default_context()?;
let mut module = runestick::Module::default();
module.function(&["print_fancy"], print_fancy)?;
context.install(&module)?;
let script = read_to_string("scripts/rune-sample.rn")?;
let mut warnings = rune::Warnings::new();
let unit = rune::load_source(&context, &Default::default(), Source::new("scripts/rune-sample.rn", &script), &mut warnings)?;
let vm = Vm::new(Arc::new(context), Arc::new(unit));
Ok(vm)
}
fn handle_event(vm: &mut Vm, event: Event) -> Result<(), Self::Error> {
let arg = match event {
Event::Number(n) => Value::from(n),
Event::Text(t) => Value::from(t)
};
vm.clone().call(&["handle_event"], (arg,))?.complete()?;
Ok(())
}
}
Again, we have zero glue required for our `print_fancy` function, which is nice. Our script looks pretty decent too:
fn handle_event(event) {
match event {
n if n is int => println(`Number! {n}`),
t => println(`Text! {t}`),
}
print_fancy("got an event!")
}
The one issue I notice is that `Vm::call` takes `self` by value, which means we're forced to `clone()` our VM every time we want to handle a new event instead of handling them all in the same VM. This probably means a more complicated script with some global state to maintain wouldn't be able to do that. So if that's something you know you'll want to support, Rune might not be for you, unless there's a different way to do this that I didn't find in the docs. But if you know you don't need that, Rune might work well for you. ## ruwren ruwren provides bindings to the Wren embeddable scripting language, which looks kinda neat: => https://crates.io/crates/ruwren ruwren => https://wren.io/ Wren > Think Smalltalk in a Lua-sized package with a dash of Erlang and wrapped up in a familiar, modern syntax. I think the example presented in the README at time of writing is incomplete and buggy, but I figured out how to fix it and sent in a pull request. We do have to write some glue for our `print_fancy` method; since Wren is inspired by Smalltalk, everything is objects, so we need a class within which `print_fancy` can be a static method:
struct Demo;
impl Class for Demo {
fn initialize(_: &VM) -> Self {
Demo
}
}
impl Demo {
fn print_fancy(vm: &VM) {
let text = get_slot_checked!(vm => string 1);
print_fancy(&text);
}
}
create_module! {
class("Demo") crate::Demo => foo {
static(fn "print_fancy", 1) print_fancy
}
module => demo
}
Armed with this, the actual integration is pretty straightforward:
struct RuwrenScriptHost;
impl ScriptHost for RuwrenScriptHost {
type ScriptContext = VMWrapper;
type Error = Box<dyn Error + 'static>;
fn new_context() -> Result<Self::ScriptContext, Self::Error> {
let mut modules = ModuleLibrary::new();
demo::publish_module(&mut modules);
let vm = VMConfig::new()
.library(&modules)
.build();
vm.interpret("demo", r##"
foreign class Demo {
foreign static print_fancy(string)
}
"##)?;
let script = read_to_string("scripts/ruwren-sample.wren")?;
vm.interpret("main", &script)?;
Ok(vm)
}
fn handle_event(vm: &mut VMWrapper, event: Event) -> Result<(), Self::Error> {
vm.execute(|vm| {
vm.ensure_slots(2);
vm.get_variable("main", "EventHandler", 0);
match &event {
Event::Number(n) => vm.set_slot_double(1, *n as f64),
Event::Text(t) => vm.set_slot_string(1, t),
}
});
vm.call(FunctionSignature::new_function("handleEvent", 1))?;
Ok(())
}
}
And our test script in Wren looks nice:
import "demo" for Demo
class EventHandler {
static handleEvent(data) {
if (data is Num) {
System.print("Number! %(data)")
} else {
System.print("Text! %(data)")
}
Demo.print_fancy("got an event")
}
}
Overall, I think this is pretty good. This specific use case isn't particularly well suited to Smalltalk-style object orientation, but if yours is then this will work well for you. Plus, since Wren isn't Rust-specific, it might be slightly more portable, if that's a concern for you. ## summary So those are our options. A convenient bullet points tl;dr for you:
error: failed to run custom build command for `servo-freetype-sys v4.0.5`
Caused by:
process didn't exit successfully: `D:\Melody\Projects\we-are-not-gui-yet\target\debug\build\servo-freetype-sys-1fae054761ff82c5\build-script-build` (exit code: 101)
--- stdout
running: "cmake" <snip>
--- stderr
CMake Error: Could not create named generator Visual Studio 16 2019
this is an inauspicious beginning. one version history crawl later and it looks like my cmake is from April 2019, which is not all that old but maybe they hadn't caught up on the latest visual studio yet, who knows. this is already more work than i was prepared to do, but i've come this far, so it's time to update my cmake. okay one installer later and it's time to try again. armed with a cmake from May 2020, let's give this another shot:
error[E0433]: failed to resolve: could not find `IoReader` in `bincode`
--> C:\Users\Melody\.cargo\registry\src\github.com-1ecc6299db9ec823\webrender_api-0.60.0\src\display_list.rs:270:35
|
270 | let reader = bincode::IoReader::new(UnsafeReader::new(&mut self.data));
| ^^^^^^^^ could not find `IoReader` in `bincode`
welp. the cmake update fixed things, i guess, but now we've got a whole other pile of mess. this might be fixable, it may have been fixed by the time you read this. regardless, this library does not work for me. ## conrod next up is conrod: => https://github.com/pistondevelopers/conrod conrod > An easy-to-use, 2D GUI library written entirely in Rust. i've actually used this one before in a couple of projects, but it's been a minute, so i forget the details. they do not have a real tutorial, which is unfortunate, but they do have some examples. unfortunately, step one is to pick which of the half dozen backends i want. do i want glium or vulkano or rendy or piston? do i look like i know what a vulkano is? i just want a picture of a god dang hot dog. okay that's not quite fair, i recognize three of those and can infer from context what the fourth one is, but that's only because i've been down this road before, and i still have no clue which one is the right one to pick. all the examples live under glium, though, so let's go with that. => https://youtu.be/EvKTOHVGNbg god dang hot dog wait actually i'm staring at these examples and there's an entire ass event loop in the support code for the examples. something in here mentions a `GliumDisplayWinitWrapper` and i'm scared. i have literally used this library before - on two different projects - and i'm at a loss. i don't want to just copy and paste the examples without actually understanding what's going on, i can't understand what's going on in the examples, and there's nowhere else to get started. so there goes that i guess. ## core-foundation > Bindings to Core Foundation for macOS. oh hey, it's an OS i don't have access to at all. next. ## druid our next contender is druid: => https://linebender.org/druid/ druid > Druid is a framework for building simple graphical applications. apparently this sprung out of that vi-like text editor a couple googlers were working on, so apparently it's at least possible to use it for real software. there's a tutorial, they're on crates.io, they're describing it as "conceptually simple and largely non-magical" which i am always a fan of, i am cautiously optimistic. if we throw it in our dependencies and just see if anything breaks, we find the surprising result that everything just works. oh hey the first real chapter in the tutorial starts with > this is outdated, and should be replaced with a walkthrough of getting a simple app built and running. you love to see it. fortunately, we can just ignore that and skip to the hello world example, reproduced here in its entirety:
use druid::{AppLauncher, WindowDesc, Widget, PlatformError};
use druid::widget::Label;
fn build_ui() -> impl Widget<()> {
Label::new("Hello world")
}
fn main() -> Result<(), PlatformError> {
AppLauncher::with_window(WindowDesc::new(build_ui)).launch(())?;
Ok(())
}
and somehow, this actually works. the tutorial ends here, which is unfortunate, but there's more documentation, including explanations of core concepts with examples that are... todo lists! with even more features than what i was planning to include here! so that's convenient. the UI hierarchy is based on CSS Flexbox, which i also appreciate. this is what peak UI layout API looks like:
fn build_new_todo() -> impl Widget<TodoState> {
let textbox = TextBox::new()
.with_placeholder("New todo")
.lens(TodoState::next_todo);
let button = Button::new("Add")
.on_click(|_, data: &mut TodoState, _| data.create_todo());
Flex::row()
.with_flex_child(textbox.expand_width(), 1.0)
.with_child(button)
}
1 hour and 80 lines of code later, we've got ourselves a perfectly valid and working todo list! => /assets/2020-08-21-survey-of-rust-gui-libraries-1.png our sample druid application, showing a todo list [IMG] i'm not quite happy with this, though: we can type text and hit the button and it adds the todo, but pressing enter in the text field doesn't do anything. there's no way out-of-the-box to make that happen; let's see if we can build that ourselves. ~30 lines of code later, we've got it! the `Controller` trait is designed for exactly this sort of thing, when you need to wrap the behavior of an existing component and intercept some events to inject your own logic. if you're curious, you can take a look at the source for our druid example. => https://git.sr.ht/~boringcactus/survey-of-rust-gui-libraries/tree/main/druid-test the source for our druid example so apparently druid is actually pretty darn usable. i only have a couple tiny issues with it: 1. it doesn't use platform native UI widgets, so it doesn't look quite like a windows app should, and it won't look quite like a mac or linux app should either if i test it there. this one is a feature as far as some people are concerned, but i am not on that list. 2. accessibility features like being able to tab between UI widgets (*update 2020-12-14*: and also literally any screen reader support) are missing, so you'd have to roll those yourself in a real application. maybe they'll add that by default in future versions, maybe not, but it would be neat if it existed. 3. high-level documentation is incomplete. the individual struct/function docs are really good, but at a high level you don't really have a convenient place to jump in. i was about to add "no support for web" to that list, but even though the high-level docs don't mention it, the crate root docs and the examples do. on the plus side, it just works, and i didn't have to make any changes to my code because i use this patch to wasm-pack that lets you just use binary crates in wasm-pack even though it hasn't been merged yet upstream. on the minus side, it points everything at a `<canvas>` tag, which means you get none of the accessibility features of actually using the DOM. so that one's a mixed bag. => https://github.com/rustwasm/wasm-pack/pull/736 this patch to wasm-pack that lets you just use binary crates in wasm-pack but yeah, overall druid is perfectly usable for gui development. i was originally calling this post "we are not gui yet" but i guess we are at least a little bit gui already. pleasant surprises are the best kind. ## fltk following up that success is fltk: => https://github.com/MoAlyousef/fltk-rs fltk > The FLTK crate is a crossplatform lightweight gui library which can be statically linked to produce small, self-contained and fast gui applications. cross-platform and statically linked are both good things. the upstream FLTK website makes my eyes bleed, which is never a good sign for a UI library, but that doesn't mean much one way or the other. the simple hello world example is once again a mere handful of lines:
use fltk::{app::*, window::*};
fn main() {
let app = App::default();
let mut wind = Window::new(100, 100, 400, 300, "Hello from rust");
wind.end();
wind.show();
app.run().unwrap();
}
a downside i'm noticing already, at least compared to druid, is that everything has to be positioned manually, and we don't get any layout stuff calculated for free. a lot of wrestling later, we have a technically working implementation (source code). => https://git.sr.ht/~boringcactus/survey-of-rust-gui-libraries/tree/main/fltk-test source code => /assets/2020-08-21-survey-of-rust-gui-libraries-2.png our sample fltk application, showing a todo list [IMG] it's half as much code as the druid implementation, but part of that's because the druid implementation also preserves state information, so we could easily have added persistence without all that much work, but our fltk version does not do that and is just a pile of ui widgets. some of that code, i will say, fails to spark joy:
add_todo.set_callback(Box::new(move || {
let text = next_todo.value();
next_todo.set_value("");
let done = CheckButton::new(0, top, 400, 30, &text);
wind.add(&done);
wind.redraw();
top += 30;
}));
we have to drag that position and size around manually. i don't like that.
thread 'main' panicked at 'called `Option::unwrap()` on a `None` value', C:\Users\Melody\.cargo\registry\src\github.com-1ecc6299db9ec823\kas-text-0.1.3\src\prepared.rs:465:9
that's bad. and i don't feel like chasing down why that happens, especially because my gut says my code isn't the problem. shame, though, the widgets sure look pretty. => https://git.sr.ht/~boringcactus/survey-of-rust-gui-libraries/tree/main/kas-test my code ## neutrino next up we have neutrino: => https://github.com/alexislozano/neutrino neutrino > Neutrino is a MVC GUI framework written in Rust. ah, ol' reliable, MVC. the wiki has an actual tutorial, too, which you love to see. => https://knowyourmeme.com/memes/ol-reliable ol' reliable most of the other libraries have not made me throw around `Rc<RefCell<T>>` everywhere myself, though. but neutrino has that just all over the place. and it gets worse than you'd think. building the same example to-do list required a `Rc<RefCell<Vec<Rc<RefCell<TodoItem>>>>>` and i feel like that's bad. it definitely makes my code look terrible. => https://git.sr.ht/~boringcactus/survey-of-rust-gui-libraries/tree/main/neutrino-test my code excitingly, we now have a demo that looks bad and also doesn't work: => /assets/2020-08-21-survey-of-rust-gui-libraries-5.png our sample neutrino application, showing a todo list [IMG] excitingly, when we type some text and hit the "add" button, the text gets lost in the created todo, and i have no goddamn clue where it's going or what to do to fix it.
error[E0405]: cannot find trait `StdError` in module `serde::de`
--> C:\Users\Melody\.cargo\registry\src\github.com-1ecc6299db9ec823\serde_json-1.0.46\src\error.rs:317:17
|
317 | impl serde::de::StdError for Error {
| ^^^^^^^^ not found in `serde::de`
so yeah, we've got some nice-looking widgets, with unintuitive layout settings, broken web support, and a *lot* of glue i had to write by hand that makes the source code cluttered and messy. don't think i'd use it for anything more serious, at least as it exists right now. => https://git.sr.ht/~boringcactus/survey-of-rust-gui-libraries/tree/main/orbtk-test the source code ## qmetaobject > The qmetaobject crate is a crate which is used to expose rust object to Qt and QML. i don't want to install Qt. that sounds like a nuisance, and more importantly, if i want Travis or whatever to give me automated CI builds, i don't think it's easy to make sure Qt exists on all platforms on Travis. ## qt_widgets oh hey, more Qt API bindings! i still don't want to install Qt. ## relm > Asynchronous, GTK+-based, GUI library, inspired by Elm, written in Rust. as established, GTK+ setup on Windows is a scary nightmare hellscape. ## rust-qt-binding-generator i am so tired. ## sciter-rs i think sciter is a thing actual programs use, which is nice. however, we need not only the sciter sdk installed and available, but also GTK+, and god damn i do not want to do that. ## WebRender last, but hopefully not least, we have webrender: => https://github.com/servo/webrender webrender > WebRender is a GPU-based 2D rendering engine written in Rust. > Firefox, the research web browser Servo, and other GUI frameworks draw with it. pour one out for Servo, btw. unfortunately, the "basic" example is still 300+ lines of code. so i doubt that's gonna be useful. => https://github.com/servo/webrender/blob/master/examples/basic.rs "basic" example ## so *are* we GUI yet? well, kinda. druid works well if you want a straightforward layout experience. iced works well if you want a straightforward render-update architecture, or actual HTML elements on Web. everything else is, as of today, broken and/or more complex than i want. and if you want native ui widgets to match your platform's look and feel (*update 2020-12-14*: or be accessible at all), that's gonna be like a year away at least.</content> </entry> <entry> <title>Post-Open Source</title> <link href="gemini://boringcactus.com/2020/08/13/post-open-source.gmi" rel="alternate" type="text/gemini" title="Post-Open Source" /> <published>2020-08-13T00:00:00Z</published> <updated>2020-08-13T00:00:00Z</updated> <id>gemini://boringcactus.com/2020/08/13/post-open-source.gmi</id> <content type="text/gemini">i'm writing this like a day after big mozilla layoffs that included a lot of people working on cool and important shit. the consensus i'm seeing is that it reflects mozilla's search for profit over impact, mismanagement, and disproportionate executive compensation. this is taking place in a larger trend of corporatization of open source over the past several years, an ongoing open source sustainability crisis, and of course COVID-19, the all-consuming crisis that makes all our other crises worse. all of this was summed up most concisely by Kat Marchรกn: => https://www.fastcompany.com/90539632/mozilla-vows-mdn-isnt-going-anywhere-as-layoffs-cause-panic-among-developers big mozilla layoffs => https://twitter.com/zkat__/status/1293626135142477825 Kat Marchรกn > Imo, open source as a community endeavor is falling apart right before our eyes, and being replaced by open source as Big Corp entrenchment strategy. > > I mean it's been happening for a while, but seeing Mozilla sinking like this is just driving the point home for me. > > FOSS is dead how did we get here? where even are we? what happens next? i am incredibly unqualified to answer any of this - i didn't show up until right around the peak of SourceForge, i wasn't there for most of this - but i'm not gonna let that stop me. ## names to start this funeral service for FOSS, we have to unpack the term itself. "free and open source software" as a term already contains multitudes. on one hand, "free software", an explicitly political movement with a decidedly anti-charismatic leader. on the other hand, "open source software", defanged and corporate-friendly by design. the free software people (correctly) criticize "open source" as milquetoast centrism. the open source people (correctly) criticize "free software" as stubborn idealism fighting tooth and nail to reject the real world as it actually exists. they have as much in common as leftists and liberals (but they're more prepared to work together), and although their short-term goals were similar enough that it made sense to lump them together (hence the cooperation), now that the movement is dead i think there's more to gain from considering them separately. most software licenses that i'm going to bring up technically qualify as both, but they're popular with one or the other, so i'll refer to "free software licenses" and "open source licenses" as licenses that are more directly tied to those movements, even though any given license likely meets both definitions. i'd say free software died a while ago, and open source went horribly right. ## freedom the free software movement, for all its faults, has always known what it's about: => https://www.gnu.org/philosophy/free-sw.html.en what it's about > 0. The freedom to run the program for any purpose. > 1. The freedom to study how the program works, and change it to make it do what you wish. > 2. The freedom to redistribute and make copies so you can help your neighbour. > 3. The freedom to improve the program, and release your improvements (and modified versions in general) to the public, so that the whole community benefits. it's concise, it's understandable, and it'sโฆ kinda useless. this point was raised better by actual lawyer Luis Villa (Karl Marx slander notwithstanding), but those freedoms don't actually mean shit to the average end user. only programmers care if they have access to the source code, and most people aren't programmers. and i *am* a programmer, and i don't give a shit. the freedom to not think about my operating system and just get work done overrules all of those for me, so i use windows. like, yeah, those things are all in principle nice to have, and between two otherwise equally good programs i'd take the free one. but they're really fuckin specific things, and even if i have the freedom to do them i'm not likely to have the ability or desire to do them, so there's no good reason for me as a user to use software that's worse in other ways because it gives me freedoms i don't need. => https://lu.is/blog/2016/03/23/free-as-in-my-libreplanet-2016-talk/ raised better by actual lawyer Luis Villa the free software movement is explicitly political, but its politics suck. it's a movement by and for ideological diehards but the ideology is extremely esoteric. theirs was a losing battle from day one. so what was it that actually killed them? i think in a very real way it was the GPLv3. ## losing the flagship projects of the free software movement are probably Linux and the GNU pile of tools. the Linux kernel being released under a free software license doesn't directly create more free software, though, since even things that tie closely to the kernel aren't obligated to also be free software, and of course user-level applications can have whatever license they want. and also most of the people using Linux right now are using it by accident, distributed as ChromeOS or Android, neither of which is free software. so Linux is a win for the free software movement but a useless one. the GNU userland tools are, for the most part, even more underwhelming. it may be technically more accurate to call it GNU/Linux, but the only time i remember my linux userland tools are GNU or free software at all is when there's some weird inconsistency between a GNU tool and its BSD equivalent, and that's not exactly ideal. gcc had, as far as i can tell, been basically *the* C compiler for a while, if you weren't stuck with MSVC or something worse. the free software movement were stubborn ideologues with weird priorities, but they still had one big technical advantage. then the GPLv3 happened. => https://twitter.com/boring_cactus/status/1166408436386430976 some weird inconsistency between a GNU tool and its BSD equivalent the GPLv2 was pretty popular at the time, but there were a couple notable loopholes some big corporations had been taking advantage of, which the free software people wanted to close. a whole bunch of people thought the GPLv2 was fine the way it was, though - closing the loopholes as aggressively as the GPLv3 did cut off some justifiable security measures, and some people said that it could do more harm than good. the linux kernel, along with a lot more stuff, declared it was sticking with the GPLv2 and not moving to the GPLv3. when your movement says "here is the new version of The Right Way To Do Things" and several of your largest adherents say "nah fuck you we're going with the old version" that is not a good sign. around the same time, free software organizations were starting to successfully sue companies who were using free software but not complying with the license. so big companies, like Apple, saw new restrictions coming in at the same time as more aggressive enforcement, and said "well shit, we want to base our software on these handy convenient tools like GCC but we can't use GPLv3 software while keeping our hardware and software as locked together as we'd like." so they started pouring money into a new C compiler, LLVM, that was instead open source. and LLVM became at least as good as GCC, and a less risky decision for big companies, and easier to use to build new languages. so the free software movement's last technical advantage was gone. its social advantages also kinda went up in flames with the GPLv3, too: the software that was the foundation for the GPL enforcement lawsuits stuck with the GPLv2. the discourse over that decision was so nasty that the lead maintainer (Rob Landley; he'll come up later) started an identical project which he wound up relicensing under an open source license because the lawsuits had completely backfired: instead of complying with the terms of the GPL, companies were just avoiding GPL software. the free software movement, in the end, burned itself out, by fighting for a tiny crumb of success and then turning around and lighting that success on fire. the death of free software tells us that we can't use a license to trick corporations into sharing our values: they want to profit, and if good software has a license that puts a limit on how much they can do that, they'll put more resources into writing their own alternative than they would spend complying with the license in the first place. ## openness the open source movement manages to share the same short term goals as the free software movement but be bad in almost entirely disjoint ways. the mission of the Open Source Initiative says => https://opensource.org/about mission of the Open Source Initiative > Open source enables a development method for software that harnesses the power of distributed peer review and transparency of process. > The promise of open source is higher quality, better reliability, greater flexibility, lower cost, and an end to predatory vendor lock-in. this is so profoundly different from the free software definition that it's almost comical. where free software says "we value freedom, which we define in these ways," open source says "your code will get better." the free software movement was prepared to start fights with corporations that used their work but didn't play by their rules. the open source movement was invented to be a friendly, apolitical, pro-corporate alternative to the free software movement. the contrast between "use free software because it preserves your freedom" and "use open source software because it's better" is profound and honestly a little disappointing to revisit this explicitly. free software preserves freedoms i don't need or care about as a user, but it does at least do that. open source software is frequently not in fact better than closed source alternatives, and "use open source software because on rare occasions it manages to be almost as good" is an even more underwhelming sales pitch than anything free software can give. where free software is misguided and quixotic, open source is spineless and centrist. and as tends to happen with spineless centrism, it has eaten the world. ## winning if there's anything corporations love more than rewriting software so it lets them make all the money they can dream of, it's letting other people do that work for them. it took a while to take off, because the conservative approach of "keep things closed source" was pretty solidly entrenched in a lot of places, but now even the once conservative holdouts have accepted the gospel of centrism. corporations have little to nothing to lose by publishing existing source code, and can gain all sorts of unpaid volunteer labor. if they start a new internal project, important enough that they're prepared to put effort into it but not so important that someone could run off with it and compete with them, then now they'll likely open source it. worst case scenario, they do all the work they were already prepared to do. best case scenario, their library turns into the single most popular library of its type, with thousands of unpaid volunteers donating their time to you. more labor for free, community goodwill for having started the project everybody uses, the benefits if it goes well are countless. free software is not in principle anti-corporate, but corporations are very cautious getting caught up in the free software movement, because that actually creates obligations for them. open source gives corporations a shot at improving their code for free, so as long as they don't share so much someone could start a competitor, so there's zero reason for a corporation to not get into open source. the best part for corporations is they don't even have to be the ones to start a project. if you're just some random small time developer, they can just show up. you made a cool database server that's under an open source license? amazon's selling it as a service now, and they're not paying you a fuckin dime. you want to change your license to stop them from doing that? now the open source people are yelling at you, because when they say they're apolitical they mean they support the status quo. and the free software people are also yelling at you, because you didn't do it their way with their license, you did it a different way with a different license, and that goes against amazon's freedom to screw you over. github itself is arguably the epitome of the open source movement. the platform itself is closed source, because they don't want people to compete with them running their code, and also they sell the very expensive self-hosted version to corporations. opening up the source for github itself would take a chunk out of github's profits. can't have that. but they don't even need to start or adopt an open source component to profit off other people's labor: *literally every project on github* makes github more valuable. popular projects get people in, network effects bring their colleagues in, and then when it's time for something that you'd rather have closed source you and everyone else are already on github so you might as well spring for the paid tier. if they believed open source was in principle better, they'd be open source themselves. they believe open source is profitable for them, and corporate profit is by definition value generated by labor but not paid to the laborer. what's good for corporations is, of course, bad for people. random individual contributors almost never get paid for their work, even when a corporation or several will profit substantially from those changes. maintainers of vital infrastructure libraries generally only get paid if they wrote the library for or under the control of the company they worked for anyway. professional, corporate maintainers can offer more to the community since they're getting paid for it, which heightens expectations on independent maintainers and leads to maintainer burnout. and if a company runs off with some existing open source software, they can build their secret competitive advantage around it without giving any of that work back to the original authors. all of these individual crises are by design: this was always the endgame of the open source movement. the free software movement was transparent with its greatest value: "we believe users should have the freedom to mess with and contribute to the source code of the programs they use." the open source movement had a far subtler value: "we believe corporations should have the freedom to exploit the labor of developers." the fact that individual developers were ever on board with the open source movement speaks to the pernicious branding it employs. but people are starting to notice that this isn't actually good at all. the free software movement was on occasion writing actually good software; corporations saw that and wanted to get in on it without having to actually have principles. so they embraced the nominal goals of the free software movement and extended it into a more corporate-friendly movement with a larger pile of software to draw from. the conventional step after embrace and extend is, naturally, extinguish. the free software movement died long ago, in no small part due to its own mistakes, so there's not much left to extinguish. that which is being extinguished, that which died with mozilla, is the idea that the open source movement could have any other principles than corporate exploitation. => https://en.wikipedia.org/wiki/Embrace,_extend,_and_extinguish naturally i wouldn't say that the open source movement died per se. it was undead from the moment it began; it won, and with its victory it has stopped pretending to be anything other than a lich. the only meaningful lesson to learn from the open source movement is that letting corporations do whatever the hell they want ends poorly, which is not exactly news. ## not learning open source won, and nothing got better. in an effort to fix this feature of the open source movement, some people have chosen to repeat the mistakes of the free software movement. as some smart german dude once said, everything in history happens twice, first as tragedy, then as farce. => https://en.wikipedia.org/wiki/Karl_Marx some smart german dude the free software movement declared that the user's freedom to tinker with and contribute to the software they use is supreme, and they wrote a license specifically built to preserve that in software applied to it, and to spread that freedom to software based on it. an uninspiring but at least well-defined goal, pursued somewhat decently, with at least some lasting success. the "ethical source" movement declares that the UN's Universal Declaration of Human Rights is supreme, with relevant laws in whatever jurisdiction is relevant a close second, and the Hippocratic License says "if the software author says you're violating human rights you have to go through public arbitration or the license is void." the goal is at least in principle better, so that's something, at least. although i will say, if someone releases a data visualization library under the Hippocratic License and someone else uses that library to display leaked personal information of police officers who got away with murder, there are several articles of the Universal Declaration of Human Rights that'd arguably be violated, so the library author would likely have grounds to make a nuisance of themself. and that sucks shit. the fact that the website for the Hippocratic License is `firstdonoharm.dev` kinda gives the whole thing away, because sometimes a little harm in one way prevents a much greater harm in some other way. there's a reason doctors don't use the hippocratic oath anymore. even setting that aside, there's a far greater issue with the Hippocratic License. show me a corporate lawyer who'll look at a license that says "i can drag you into arbitration proceedings that have to be public whenever i want and there's no consequences for me doing that in bad faith" and say "yeah that looks good, we can use this library" and i'll show you a corporate lawyer who's gonna get fired tomorrow. the free software movement tried and failed to use a license to trick corporations into sharing their values. the ethical source movement appears to be trying to use a worse license to trick corporations into sharing less concretely defined values. until all the talented people in that community start doing more useful things with their time, we can at least learn a few things from this preemptive failure. one, trying to bake the complexity of an ethical system into your license is a fool's errand that will not go well. two, if you're writing a license to coerce companies into behaving differently, don't scare them off right out of the gate with a poorly considered enforcement system. ## options the term "post-open source" apparently was used by a couple people in like 2012 to refer to just not giving your code a license. it's got a wikipedia page that's had the "this might not be notable enough for wikipedia" box applied to it since 2013. i am declaring that Basically Dead and so i'm using that term in a broader way now. => https://en.wikipedia.org/w/index.php?title=Post_open_source&amp;oldid=890953566 wikipedia page so what do we do after open source has eaten the world? the retro option, apparently, is to skip the license entirely. it'll scare off the corporations, since they technically can't safely use your work if you maintain full copyright. and as actual lawyer Luis Villa pointed out at the time, the idea that you need to give other people permission to do things like modify your code for themselves is something we shouldn't automatically take for granted. (although i must say, for someone who claims to hate "permission culture" so much, Nina Paley sure does seem concerned with giving people permission to count as women. TERFs fuck off, now and forever.) not using a license at all can be interpreted as a conscious rejection not just of copyright but also of the endeavor to wield copyright as a tool for justice at all. => https://lu.is/blog/2013/01/27/taking-post-open-source-seriously-as-a-statement-about-copyright-law/ pointed out at the time however, not using a license at all also makes it complicated for actual human beings who want to use your software. Villa points to a favorite of mine, the Do What The Fuck You Want To Public License, as a way to make the implicit permissiveness of rejecting licensing altogether explicit while preserving the anti-serious aspect. however, once the corporations realize that they're allowed to use software that says fuck, they can and will exploit the shit out of WTFPL software, so this does not provide a long term solution for the problems with open source. (it is, however, really good, so i will count it as post-open source at heart even though it is essentially just open source). its nominally equivalent but more serious cousin, zero-clause BSD, was written by the same Rob Landley whose experience navigating GPLv2 vs GPLv3 was so unpleasant back in the day; it's no fun, and i wouldn't call it a post-open source license, but it is in a very real way a post-free software license, and the exact opposite of the GPL. and in fairness i'd be trying to write the opposite of the GPL after that mess too. => http://www.wtfpl.net/ Do What The Fuck You Want To Public License => http://landley.net/toybox/license.html zero-clause BSD the ethical source people are trying to use the hippocratic license to make it illegal to use certain software if you're doing bad things. the issues with that were the broad definition of "bad things" and the weird enforcement provisions. you can take both of those to the other extreme and get the JSON License. it's just a regular MIT/BSD/X11/whatever permissive license but with an extra caveat: => https://www.json.org/license.html JSON License > The Software shall be used for Good, not Evil. now, this is basically decorative (although evidently IBM paid the author for permission to do evil with that software, which is *fucking beautiful*), but it does also scare off corporations while letting normal people do whatever. i actually had a brief twitter exchange with the unparalleled jenn schiffer about the effectiveness of the json license a while back, but she understandably doesn't let ancient tweets linger forever, so whatever actual points were made there are lost to time. it does at least manage to solve the problems with the hippocratic license, though: the definition of evil is left completely implicit anyway, and the mechanism for enforcement is just copyright law like with any old license. now, since the vagueness is left implicit, there's room to argue that the clause is unenforceable. nobody has tested it, but that's a loophole waiting to be exploited, and also it's not as fun as the WTFPL. as such, right before i started writing this blog post i wrote the fuck around and find out license v0.1 (or FAFOL for short), which replaces the json license's ethics disclaimer with something more clear: => https://twitter.com/boring_cactus/status/1090803883230679040 brief twitter exchange => https://git.sr.ht/~boringcactus/fafol/tree/master/LICENSE-v0.1.md fuck around and find out license v0.1 > the software shall be used for Good, not Evil. the original author of the software retains the sole and exclusive right to determine which uses are Good and which uses are Evil. now it is unambiguous in its intent, and also, it says fuck in the title. as such, it is the only good software license.
trait Monad {
// TODO
}
Rust has two types that will be helpful here, because (spoilers) it turns out they're both monads: `Vec` and `Option`. now, if you've worked with Rust before, you might be thinking "wait, don't you mean `Vec<T>` and `Option<T>`?" and that's a reasonable question to ask, since Rust doesn't really let you just say `Vec` or `Option` by themselves. but as it happens, the monad-ness applies not to a specific `Vec<T>` but to `Vec` itself, and the same goes for `Option`. which means what we'd like to do is say
impl Monad for Vec {}
impl Monad for Option {}
but Rust won't let us do that because we can't talk about `Vec`, only `Vec<T>`. this is (part of) why Rust doesn't have monads. so let's just kinda pretend that's legal Rust and move on. what operations make a monad a monad? ## new Wadler calls this operation `unit`, and Haskell calls it `return`, but i think it is easier to think of it as `new`.
trait Monad {
fn new<T>(item: T) -> Self<T>;
}
`new` takes a `T` and returns an instance of whatever monad that contains that `T`. it's pretty straightforward to implement for both `Option` and `Vec`:
impl Monad for Option {
fn new<T>(item: T) -> Self<T> { Some(item) }
}
impl Monad for Vec {
fn new<T>(item: T) -> Self<T> { vec![item] }
}
we started out with a stuff, and we made an instance of whatever monad that contains that stuff. ## flat_map Wadler calls it `*`, Haskell calls it "bind" and spells it `>>=`, but i think `flat_map` is the best name for it.
trait Monad {
fn flat_map<T, U, F: Fn(T) -> Self<U>>(data: Self<T>, operation: F) -> Self<U>;
}
we have an instance of our monad containing data of some type `T`, and we have an operation that takes in a `T` and returns the same kind of monad containing a different type `U`. we get back our monad containing a `U`. as you may have guessed by how i named it, `flat_map` is basically just `Iterator::flat_map`, so implementing it for `Vec` is fairly straightforward. for `Option` it's literally just `and_then`.
impl Monad for Option {
fn flat_map<T, U, F: Fn(T) -> Self<U>>(data: Self<T>, operation: F) -> Self<U> {
data.and_then(operation)
}
}
impl Monad for Vec {
fn flat_map<T, U, F: Fn(T) -> Self<U>>(data: Self<T>, operation: F) -> Self<U> {
data.into_iter().flat_map(operation).collect()
}
}
so in theory, we're done. we've shown the operations that make a monad a monad, and we've given their implementations for a couple of trivial monads. but not every type implementing this trait is really a monad: there are some guarantees we need to make about the behavior of these operations. ## monad laws (written with reference to the relevant Haskell wiki page) => https://wiki.haskell.org/Monad_laws the relevant Haskell wiki page like how there's nothing in Rust itself to ensure that your implementation of `Add` doesn't instead multiply, print a dozen lines of nonsense, or delete System32, the type system is not enough to guarantee that any given implementation of `Monad` is well-behaved. we need to define what a well-behaved implementation of `Monad` does, and we'll do that by writing functions that assert our `Monad` implementation is reasonable. we're going to have to also cheat a bit here and deviate from actual Rust by using `assert_eq!` to mean "assert equivalent" and not "assert equal"; that is, the two expressions should be interchangeable in every context. first off, we have the "left identity," which says that passing a value into a function through `new` and `flat_map` should be the same as passing that value in directly:
fn assert_left_identity_holds<M: Monad>() {
let x = 7u8; // this should hold for any value
let f = |n: u8| M::new((n as i16) + 3); // this should hold for any function
assert_eq!(M::flat_map(M::new(x), f), f(x));
}
next, we have the "right identity," which says that "and then make a new monad instance" should do nothing to a monad instance:
fn assert_right_identity_holds<M: Monad>() {
let m = M::new('z'); // this should hold for any instance of M
assert_eq!(M::flat_map(m, M::new), m);
}
and last but by no means least we have associativity, which says it shouldn't matter the sequence in which we apply `flat_map` as long as the arguments stay in the same order:
fn assert_associativity_holds<M: Monad>() {
let m = M::new(false); // this should hold for any instance of M
let f = |data: bool| if data { M::new(3usize) } else { M::new(7usize) }; // this should hold for any function
let g = |data: usize| M::new(vec!["hello"; data]); // this should hold for any function
assert_eq!(
M::flat_map(M::flat_map(m, |x: bool| f(x)), g),
M::flat_map(m, |x: bool| M::flat_map(f(x), g))
);
}
so now we can glue all those together and write a single function that ensures any given monad actually behaves as it should:
fn assert_well_behaved_monad<M: Monad>() {
assert_left_identity_holds::<M>();
assert_right_identity_holds::<M>();
assert_associativity_holds::<M>();
}
## but. why well. monads exist in functional programming to encapsulate state in a way that doesn't explode functional programming (among other things, please do not @ me). Rust isn't a functional programming language, so we have things like `mut` to handle state. there's a bit of discussion in Rust abt how monads would be actually implemented - the hypothetical extended Rust that i use here is not actually what anyone advocates for, you can look around for yourself if you care - but even the people in that discussion seem to not really explain why Rust needs monads. so all of this doesn't really build up to anything. but hey, now (with luck) you understand what monads are! i hope you find that rewarding for its own sake. i hope i do, too.</content> </entry> <entry> <title>What, Then, Shall We Do?</title> <link href="gemini://boringcactus.com/2020/07/15/what-then-shall-we-do.gmi" rel="alternate" type="text/gemini" title="What, Then, Shall We Do?" /> <published>2020-07-15T00:00:00Z</published> <updated>2020-07-15T00:00:00Z</updated> <id>gemini://boringcactus.com/2020/07/15/what-then-shall-we-do.gmi</id> <content type="text/gemini">america is at a crossroads of sorts. widespread dissatisfaction with how governments at all levels handled the COVID-19 pandemic, decades of police brutality overwhelmingly targeting POC, two presidential candidates nobody (or at least nobody worth a damn) really likes. we (by which i mean the american left, broadly speaking) see these futures unfold before us, and ask ourselves "what, then, shall we do?" i'm just some random dipshit, i'm not qualified to give an answer, but i can think about what we shall do specifically when it comes to the upcoming presidential election. ## the very marginally lesser of two evils obviously voting for donald trump is inexcusable. he's a deranged senile incestuous racist rapist whose occasional halfhearted gestures towards the working class are transparently borne of a desire for self-preservation rather than any actual good will. he may occasionally claim to be an ally for queer people, or people of color, or women, but his record stands in contrast to any of these claims - he has done more material harm to these groups than he has even bothered to acknowledge - and so only profound gullibility could lead anyone to believe this. even the most negligible support of him is also a tacit acceptance of the entire political system that allowed him to gain what power he already has, and trusting that system to hold him in check is foolish. oh shit did i say donald trump there? i meant joe biden. and also donald trump. ## the least of a dozen evils so joe biden sucks, and we cannot start ridin with biden. the democratic primary was what could generously be described as a mess, with the maybe-depending-on-who-you-ask winner of the first primary dropping out early to try to donate his momentum to an even more useless, even more racist moderate. the party establishment threatened to punish states that postponed their primaries in light of an ongoing pandemic that made it unsafe to vote, contributing to widespread voter suppression. clearly the democratic party is no friend to the left, and cannot be trusted. so what other parties can we support? well, about that. let's look at the third parties that were around in 2016. the libertarian party is - setting aside the libsoc caucus, who are probably cool but wasting their time - just for conservatives who want to smoke weed, and the constitution party is for religious conservatives who are too cool for the republican party, so neither of those is worth a damn. we can also ignore the tiny parties that only exist in like three states. that leaves us with like two third parties that could conceivably be worth caring about. => https://en.wikipedia.org/wiki/Third-party_and_independent_candidates_for_the_2016_United_States_presidential_election third parties that were around in 2016 most notably, we've got the green party. on the ballot almost everywhere, which is nice. they got like 2.5% of national votes back in 2000, and they cleared 1% in 2016. their 2020 nominee is Howie Hawkins, who apparently worked with Murray Bookchin for a while, and as a communalist myself i gotta say that's pretty epic. however. according to somebody who was running in the green primary and lost, there were some party-level shenanigans pulled to swing the process towards hawkins - local parties officially hosting hawkins' campaign events before the primary was held, party officials also being hawkins campaign staff, etc. considering that a lot of ppl are mad at the democratic party for pulling the same shit, that's not great. and also hawkins has been kinda both sidesy abt antivaxx stuff, which is cringe. you're allowed to not care if you want, but i'm not sure "get the greens past the 5% threshold so they get federal funding in 2024" is really a capital-S Solution. in the other corner we have the Party for Socialism and Liberation. socialism and liberation are both cool things to have. also their vice presidential candidate is Leonard Peltier, a political prisoner allegedly involved in the murder of two FBI agents, so that's pretty based. however, they were only on the ballot in a handful of states in 2016, so they may or may not even have enough reach to be worth caring about even if they nominated Marx and Engels themselves. also even in the states where they exist they haven't managed to achieve any meaningful electoral results. and one of their most prominent members, Michael Prysner, is a military intelligence war crimes doer; transformative justice is a cool thing and all but for fuck's sake assume military intelligence dudes are compromised six ways from sunday and don't let them have major influence in your vanguard. again, feel free to not care, but i do. there are non-political-party organizations like the DSA, which i'm a dues-paying member of, but i don't think pivoting one of them into a political party or spinning up something new would really help either. ballot access is key to making a case for having a chance to win despite being a third party, and even the DSA couldn't get that in most states on a shorter than several years scale. so. the green party is recreating the favoritism that was part of why we're pissed off at the democratic party, and the PSL is hanging out with war criminals while being missing or useless everywhere, and anything else will take eons to get off the ground and so doesn't give us anything to do in the meantime. what, then, shall we do? ## no fate but what we make for ourselves maybe electoralism is a waste of energy. maybe voting is a waste of time. when we vote, we don't get shit. when we take to the streets and burn shit down, we are heard. politicians already don't feel obligated to try to earn your vote. make them earn peace. they should be afraid of us, them and the cops and everyone who works to defend and prop up the indefensible on a daily basis. there are more of us than there are of them. violence is bad but complacency is worse. in the 60s it was the ballot or the bullet, and comrades, i don't think the ballot has done what it needed to. => https://en.wikipedia.org/wiki/The_Ballot_or_the_Bullet the ballot or the bullet</content> </entry> <entry> <title>Setting Up A Police Scanner With An RTL-SDR</title> <link href="gemini://boringcactus.com/2020/06/26/police-scanner-setup.gmi" rel="alternate" type="text/gemini" title="Setting Up A Police Scanner With An RTL-SDR" /> <published>2020-06-26T00:00:00Z</published> <updated>2020-06-26T00:00:00Z</updated> <id>gemini://boringcactus.com/2020/06/26/police-scanner-setup.gmi</id> <content type="text/gemini">so my city has a community-run police scanner broadcast on the internet, but the person who runs it is a bootlicker who's been threatening to shut it down if people are using it to make trouble for the cops. so i figured i'd set up my own. this is how i did it, hope it's useful. ## shopping you'll need an RTL-SDR unit. i recommend the dipole antenna kit as well, so you don't need to make any additional purchases. if you're a radio enthusiast already, you might have a better antenna available, but if you're like me you do not and it's worth the US$10. mine took a bit over a week to arrive. if you're extremely unlucky, you might need two of them, but i was fine with just one. => https://www.rtl-sdr.com/buy-rtl-sdr-dvb-t-dongles/ an RTL-SDR unit ## basic setup once your RTL-SDR arrives, you'll want to put together your antenna. if you're lucky, like i am, you can just extend the antennas arbitrarily and it'll work fine; if you're cursed, the RTL-SDR website has resources on how long is ideal for various frequencies. connect the antenna to the RTL-SDR unit, plug it in, and follow the RTL-SDR quick start guide. SDRSharp will work, or any of the other Windows options. some of what we'll need is only available on windows. => https://www.rtl-sdr.com/rtl-sdr-quick-start-guide/ quick start guide once your RTL-SDR's drivers are sorted out, find the specifications for police radio in your area on RadioReference. click your state, click your county, scroll down and see if there's a link above a frequency table for you. if you're lucky, there is, and if you click it there's a page with a table with System Type and System Voice entries at the top. mine has a system type of EDACS Networked Standard and a system voice of ProVoice and Analog, so the rest of this assumes that's what you've got as well. if not, good luck. => https://www.radioreference.com/apps/db/ RadioReference there should be a table for System Frequencies on your RadioReference page. start up SDRSharp and tune your radio to the first frequency listed there. you'll probably hear a bunch of static and the UI will look something like this: => /assets/2020-06-26-police-scanner-setup-1.png one constant signal and a bunch of other signals coming in and out at other frequencies [IMG] see how there's one constant signal and a bunch of other signals that appear and disappear all over the place? well, that's trunking, and the constant signal is our *control channel*. if you don't see it, you can click and drag on the bottom axis of the top panel to change the view. once you've found that constant signal, click on it to get the approximate frequency, go back to your frequency table and the closest thing to that will be the exact frequency. it should sound like a series of weird beeps instead of static. remember that frequency, it'll be important later.
DSDPlus.exe -i3M
in that file: if yours is not 3, put whatever the correct number is for you instead of 3. then, save the file, find your DSDPlus folder, make sure the type is set to "All Files", and name the file `run.bat`. close dsd+, go to that folder, and open that `run.bat` file you just created. it should pull up dsd+ and if you're lucky it'll print
audio input device #3 (CABLE Output (VB-Audio Virtual ) initialized
or something like that. leave that open. open up unitrunker. click the `+` to add a new receiver, and click the RTL2832 button to add your RTL-SDR. set your settings around like this: => /assets/2020-06-26-police-scanner-setup-2.png the RTL-SDR settings [IMG] the most important things are the RTL Device, the sample rate (2.56 msps), and the VCOs (2 VCOs). i do not know what a VCO is and i do not care enough to find out. we should now have two VCO tabs next to our info tab. the first one needs to look kinda like this: => /assets/2020-06-26-police-scanner-setup-3.png the RTL-SDR VCO 1 settings [IMG] the important things are the Role being Signal, the Park frequency being the control channel we found earlier (mine is 851.7625), and the Mute box being checked.
DSDPlus.exe -i3M -o4
run butt, pull up the settings, and under the Audio tab set the Input Device to "CABLE-A Output". (for bonus points, set the Streaming Codec to AAC+.) under the Main tab, Add a new Server and put in whatever info your icecast server admin told you to use. now restart your DSD+ and hit butt's play button to start streaming, and you should be running a livestream of your police scanner that is accessible over the internet.</content> </entry> <entry> <title>Lifehack: Running An Entire Desktop Session Remotely With MobaXterm</title> <link href="gemini://boringcactus.com/2020/03/20/mobaxterm-desktop-session.gmi" rel="alternate" type="text/gemini" title="Lifehack: Running An Entire Desktop Session Remotely With MobaXterm" /> <published>2020-03-20T00:00:00Z</published> <updated>2020-03-20T00:00:00Z</updated> <id>gemini://boringcactus.com/2020/03/20/mobaxterm-desktop-session.gmi</id> <content type="text/gemini">Since my university has gone as remote as possible due to coronavirus, I was looking at ways to run an entire desktop session remotely over SSH, using MobaXterm because it is very cool. Here are the two steps to doing that. 1. Open your MobaXterm settings, go to the X11 tab, and make sure that the server display mode is set to windowed mode. If you run individual programs over X11 forwarding, this is worse, but for an entire desktop session it is better. 2. Duplicate your regular command line session that already works, and under the "Advanced SSH settings" tab, set "Execute command" to `env GNOME_SHELL_SESSION_MODE=ubuntu gnome-session --session=ubuntu`. (If you're not running the same setup I am, look around in `/usr/share/xsessions/`, pick something that looks reasonable, and use everything after `Exec=` on the line with that.) At this point, you should be set. You'll need to hit the "log out" button to smoothly exit the connection. For me, this is extraordinarily slow, but that could easily be just because the machines I'm connecting to are being used a lot.</content> </entry> <entry> <title>Rust 2020: Write Once, Run Anywhere</title> <link href="gemini://boringcactus.com/2019/11/03/rust-2020.gmi" rel="alternate" type="text/gemini" title="Rust 2020: Write Once, Run Anywhere" /> <published>2019-11-03T00:00:00Z</published> <updated>2019-11-03T00:00:00Z</updated> <id>gemini://boringcactus.com/2019/11/03/rust-2020.gmi</id> <content type="text/gemini">thing that is cool: writing the same codebase and having it run on desktop, mobile, and web thing that is lame: JavaScript is the only language where people really do that right now, outside of big commercial game engines things that need to happen for Rust to get there: 1. promote more platforms to tier 1, or maybe introduce a "tier 1.5" where std is guaranteed to work but rustc and cargo are not (although it'd be cool for rustc to work on WebAssembly) * iOS: `aarch64-apple-ios`, `armv7-apple-ios`, `i386-apple-ios`, `x86_64-apple-ios` * Android: `aarch64-linux-android`, `arm-linux-androideabi`, `i686-linux-android`, `thumbv7neon-linux-androideabi`, `x86_64-linux-android` * WebAssembly: `wasm32-unknown-unknown` (or one of the other `wasm32` targets) 2. test platform abstractions (graphics libraries, game engines, UI frameworks) on all of those 3. get some high-level examples together of how to use Rust to write performant cross-platform code</content> </entry> <entry> <title>Email Notifications for SSH Logins From Scratch</title> <link href="gemini://boringcactus.com/2019/03/09/email-on-ssh-auth.gmi" rel="alternate" type="text/gemini" title="Email Notifications for SSH Logins From Scratch" /> <published>2019-03-09T00:00:00Z</published> <updated>2019-03-09T00:00:00Z</updated> <id>gemini://boringcactus.com/2019/03/09/email-on-ssh-auth.gmi</id> <content type="text/gemini">I just spent a while trying to make this happen, so I'm putting this here so I don't have to redo all that research next time. ### Configuring Email This guide from Linode explains how to install and configure Postfix, which you'll need. Be careful, though: when it says `[mail.isp.example]` the `[]` aren't just to indicate placeholders. You do need a literal `[]` around your hostname in your Postfix configuration. => https://www.linode.com/docs/email/postfix/postfix-smtp-debian7/ This guide from Linode Also, if your setup is like mine, if you try to send email from `x@<your domain>` to `y@<your domain>`, Postfix will unhelpfully try to deliver it locally. This Server Fault answer explains how to tell Postfix to not do that. => https://serverfault.com/a/433305 This Server Fault answer If you're really unlucky, you may also need to create local users `x` and `y` (with `useradd -M -N -s /bin/false <username>`). I did that before I fixed the Postfix config, so fixing the Postfix config may be enough. ### Configuring SSH Thankfully, by the time you've got the email configuration out of the way, this guide from VPSInfo fully explains how to set up SSH to send emails on login. This will send emails even if no login shell is run on the ssh connection. => https://www.vpsinfo.com/tutorial/email-alert-ssh-login/ this guide from VPSInfo Since you need to store your credentials in the Postfix configuration, a sufficiently motivated attacker could probably retrieve them. As such, if you're using email notifications to detect security breaches, I would suggest not sending them to the same address that they're being sent from. As a security measure, this is purely reactive; you can know that someone has illegitimately connected, but whatever they're trying to do has already been done. A proactive measure would be to implement 2FA on SSH logins, as per this guide from DigitalOcean. => https://www.digitalocean.com/community/tutorials/how-to-set-up-multi-factor-authentication-for-ssh-on-ubuntu-16-04 this guide from DigitalOcean</content> </entry> <entry> <title>Announcing vidslice</title> <link href="gemini://boringcactus.com/2019/02/27/vidslice.gmi" rel="alternate" type="text/gemini" title="Announcing vidslice" /> <published>2019-02-27T00:00:00Z</published> <updated>2019-02-27T00:00:00Z</updated> <id>gemini://boringcactus.com/2019/02/27/vidslice.gmi</id> <content type="text/gemini">I just released version 1.0 of vidslice, a wxPython GUI that wraps ffmpeg and youtube-dl to make "give me from 1:03-1:15 of this youtube video" really easy to do. More details are in the project README. => https://github.com/boringcactus/vidslice vidslice</content> </entry> <entry> <title>Futures</title> <link href="gemini://boringcactus.com/2018/08/31/futures.gmi" rel="alternate" type="text/gemini" title="Futures" /> <published>2018-08-31T00:00:00Z</published> <updated>2018-08-31T00:00:00Z</updated> <id>gemini://boringcactus.com/2018/08/31/futures.gmi</id> <content type="text/gemini">I'm going to be graduating from college in December and I'm a little bit freaking out. Not because I don't have a plan, but because I have six plans. ## Melody the grad student I may hate college, but that doesn't mean I'm not good at it. I'm still not entirely sure if it's college in general I hate or just my college in particular, but I've got a hunch it's the latter. In that case, I could absolutely try to get a master's in computer science at a halfway decent university. This would be the next step towards getting a Ph.D. and becoming a computer science professor, which is a thing that I think would be super cool. However, the step itself would take a whole bunch of work and I don't necessarily have all that much interest in CS theory. Someone suggested that I could pursue a master's in philosophy instead, which is an intriguing but counterintuitive prospect that I haven't necessarily thought all the way through yet. I don't think I could take another semester of college right now, but most places are probably set up for people to start in the fall and not the spring, so that works out. ## Melody the indie miscellany dev I am a walking collection of half-baked project ideas waiting to be built. Web application that bridges Twitter content into the ActivityPub/OStatus fediverse (Mastodon et al). No-code-required Discord bot toolkit. TodoMVC but for desktop GUI frameworks. => http://todomvc.com/ TodoMVC If I had the time, I could actually build some of those things. I'd need to make money somehow, though; if this were me full time I'd probably be on Patreon or trying to figure out non-shady ways to monetize this stuff. However, being solely responsible for things that people actually use would be super stressful. Plus I don't think I have the discipline to do anything if there's nobody to make me do it. ## Melody the indie game dev Games are cool. I've made a handful already for game jams and whatnot. Actually, the first thing I actually did as boringcactus was make a game for a game jam. The trouble is that I don't have a big-I Idea that I would pursue if I decided I wanted to be an indie dev. Most of my jam games are either finished or not worth finishing. I'd love to make something that's an indie success story like Night in the Woods or Gunpoint, but those games already exist. Plus the odds of taking off like that are kinda one-in-a-million. ## Melody the musician
5. Paste in this big ol' block of code and let it build your tiles and print out the text you can enter in Discord to reconstitute your original image:
There are two minor issues with actually using this code to convert a Telegram sticker into Discord emoji that I'll get to later. ## The Code, Splained I'll walk through each bit of the code segment above and explain why it's there. We need the GIMP libraries, the Python 3 `print()` function (because as of GIMP 2.8.22 the GIMP console is still on Python 2), and some path manipulation functions.
from gimpfu import *
from __future__ import print_function
import os.path
We're going to crop an image with an X and Y offset and a width and height. The first step in generating the tile is telling GIMP to do the actual crop.
def crop(image, x, width, y, height):
pdb.gimp_image_crop(image, width, height, x, y)
The next step is to figure out the filename for this specific tile; here we're getting an index back from the offsets and width and height.
x_idx = x / width + 1
y_idx = y / width + 1
filename = pdb.gimp_image_get_filename(image)
dir, name = os.path.split(filename)
root, ext = os.path.splitext(name)
ext = ".png"
output_root = root + "_" + str(y_idx) + "_" + str(x_idx)
output_name = os.path.join(dir, output_root + ext)
Once we've got a filename, we can save. For some reason GIMP's save functions all depend on both the image and the layer, and on two copies of the filename.
layer = pdb.gimp_image_get_active_layer(image)
pdb.file_png_save_defaults(image, layer, output_name, output_name)
Since the goal is to reconstitute the original image from Discord emoji, we assume that they won't be renamed. We need the Python 3 print function here to suppress any characters after the string is printed; the Python 2 `print "foo",` trick still emits a space.
print(":" + output_root + ":", end="")
We might as well delete the image from GIMP. I don't know if this actually serves an important purpose or not.
pdb.gimp_image_delete(image)
We want to grab the original filename.
image = gimp.image_list()[0]
filename = pdb.gimp_image_get_filename(image)
Since we defined WIDTH and HEIGHT manually earlier, now we can loop through the image. I should probably go back in and make it grab the full image width and height, but fuck it, I don't want to.
for y in range(0, 512, WIDTH):
for x in range(0, 512, HEIGHT):
I don't know if GIMP doesn't expose undo in the Python API or if I just couldn't find it, but either way we don't have undo, so we pass in a fresh copy of the image instead.
crop(pdb.gimp_file_load(filename, filename), x, WIDTH, y, HEIGHT)
Since we're building up the emoji text for Discord one row at a time, we need to end the row at the end of a row.
print()
This is just there so the newline after the `for` loop gets pasted successfully.
pass
## The Plot Thickens The first issue with this approach is that Discord (at time of writing, at least) sets a total of 2.25 pixels worth of horizontal margin between emoji, so your reconstituted image will have weird stripes. It might be feasible to adjust for these in the offsets so that the spacing isn't funky, but honestly that seems like a lot of work. The second, and more interesting, issue is that Discord has a 50 emoji limit on each server. Slicing a 512x512 image into 32x32 tiles for a full size replica would generate 256 tiles, which might work if you had Discord Nitro and six different dummy servers, but nah. Slicing into 64x64 tiles that'll be rendered at half size only makes 64 tiles, which works out nicely numerically but is still more than can fit on one server. Unless we're clever. I'm not sure how well this generalizes, but for the sticker I'm working with, 16 of those 64 tiles are fully transparent, and therefore identical. If we could detect this when slicing, we could avoid emitting 15 of those, at which point we come in nicely with 49 tiles, one under the Discord emoji limit. But how can we detect if an image is fully transparent? Get histogram info for the alpha channel! We can use something like this to count how many pixels aren't fully transparent:
_, _, _, _, visible_count, _ = pdb.gimp_histogram(layer, HISTOGRAM_ALPHA, 1, 255)
So our final code can detect if each tile is fully transparent before it saves and treat all fully transparent tiles as equivalent to the very first one.
from gimpfu import *
from __future__ import print_function
import os.path
empty_tile_name = None
def crop(image, x, width, y, height):
global empty_tile_name
pdb.gimp_image_crop(image, width, height, x, y)
layer = pdb.gimp_image_get_active_layer(image)
_, _, _, _, visible_count, _ = pdb.gimp_histogram(layer, HISTOGRAM_ALPHA, 1, 255)
x_idx = x / width + 1
y_idx = y / width + 1
filename = pdb.gimp_image_get_filename(image)
dir, name = os.path.split(filename)
root, ext = os.path.splitext(name)
ext = ".png"
output_root = root + "_" + str(y_idx) + "_" + str(x_idx)
output_name = os.path.join(dir, output_root + ext)
if visible_count > 0 or empty_tile_name is None:
pdb.file_png_save_defaults(image, layer, output_name, output_name)
if visible_count == 0:
if empty_tile_name is None:
empty_tile_name = output_root
else:
output_root = empty_tile_name
print(":" + output_root + ":", end="")
pdb.gimp_image_delete(image)
image = gimp.image_list()[0]
filename = pdb.gimp_image_get_filename(image)
for y in range(0, 512, WIDTH):
for x in range(0, 512, HEIGHT):
crop(pdb.gimp_file_load(filename, filename), x, WIDTH, y, HEIGHT)
print()
pass
The results are actually fairly impressive, all things considered: => /assets/2018-06-23-slicing-images-gimp-python-1.png A halfway decent but slightly stripe-y replica as Discord emoji of the Telegram sticker of Pandora's Fox dabbing. [IMG] (that sticker is by NL and of Pandora's Fox) => https://twitter.com/NLDraws NL => https://twitter.com/pandoras_foxo Pandora's Fox But of course anyone with an ounce of sense would just upload the image so this whole project was a complete waste of three hours.</content> </entry> <entry> <title>Windows, Vim, and Python: An Unholy Trinity of Pain</title> <link href="gemini://boringcactus.com/2018/06/17/windows-vim-python.gmi" rel="alternate" type="text/gemini" title="Windows, Vim, and Python: An Unholy Trinity of Pain" /> <published>2018-06-17T00:00:00Z</published> <updated>2018-06-17T00:00:00Z</updated> <id>gemini://boringcactus.com/2018/06/17/windows-vim-python.gmi</id> <content type="text/gemini">Last summer I figured I'd learn Vim. That did not go well. I started by stealing somebody's `.vimrc`, as is natural. In this case the person from whomst I lifted my `.vimrc` was a Python dev, and I was working in Python at the time, so that was a reasonable choice. But once I opened an actual Python file I got an error message that Vim couldn't find Python. I did some research and it turned out that even though I'd grabbed the latest version of Vim, it was looking for Python 3.5 and I had Python 3.6, which had been out for a while by then. So I uninstalled Python 3.6 and installed Python 3.5 and started getting a different error message. A bit more research revealed that my Python was 64-bit but my Vim was 32-bit. Apparently Vim didn't provide official 64-bit Windows builds at that time, so for 64-bit Vim on Windows they just linked to a handful of third party distributions. I went ahead and uninstalled my 32-bit Vim so I could install 64-bit Vim, and then everything worked fine. (Except for all the minor Vim papercuts that eventually led me to write my own Nano clone instead.) => https://github.com/mathphreak/mfte my own Nano clone To get Vim and Python to play nice with each other, I had to reinstall both of them. And that's basically what developing on Windows is like in a nutshell. But it doesn't have to be this way. If more people treated Windows as a first class platform, the tools to develop on Windows wouldn't be so frustrating to use, and then more people would treat Windows as a first class platform.</content> </entry> </feed>