Luminescent Dreams

A bit of freehand sketching

As a change of pace, today I’ll share a rough freehand sketch that I made this morning. I was sitting in my favorite spot in my living room and decided to do a quick fifteen minute “speed sketch”. This one depicts my TV, the stand it sits atop, and the various doorways leading out of the room. It basically skips out on all of the details sitting around.

I was good at sketching back in high school when I took a semester-long class of it, but I’ve largely stopped doing it. However, my partner has decided to start sketching as a way of driving her artwork, and I just added wireless capability to my wacom tablet, so I’ve suddenly decided to do some of the same. Though perhaps not at the one-a-day rate that she’s going for.

Art is something I’ve been neglecting, and I note that I don’t think I have taken one single photograph with my good camera yet this year.

So, in penance, I will share a phone photo of Sapphira looking all comfortable.

Fitnesstrax 0.0.4 and GTK

I finally return after a bunch of weeks. December was an incredibly hard month, and the mental health problems I referenced in my last post actually became significantly more pronounced. I found that I had to take a large amount of time away from the world in order to get things back together.

Last time I got this bad, back in 2013, I had the freedom to quit my job, sell my house, and go live in the woods for a year. Not so this time, as I still haven’t recovered from the financial impact of trying to control my re-entry into the workforce.

Nevertheless, I am probably better prepared, and better supported, that I was back in 2013.

FitnessTrax

I am pleased to finally do a new release of FitnessTrax!

This represents a significant technology shift. Instead of a web client/server application, this is now a dedicated GTK application. This should make it significantly easier for regular users, though right now I am only building and testing on Linux.

However, forward progress also means some backsliding. Here’s some differences from the previous web app:

  • You need a configuration file – fortunately, easy to set up
  • Most configuration is unsupported – you are stuck in English with the metric system, but timezones work
  • Weight, step counting, and time/distance workouts
  • No Set/rep workouts or duration-only workouts

I hope to move forward quickly on all of these things, especially now that I have largely discovered patterns that allow me to work with Rust and GTK together. Version 0.0.5 will have much better configuration support, including the ability to reconfigure and have the configuration options automatically saved.

So, to get the application, you can either get it from my Nix chcannel, or you can clone the repository and build it yourself.

FitnessTrax in Nix

This is certainly the easiest way to install FitnessTrax. Note, however, that it is most likely to work on various Linux distributions all running the Nix package manager. It may or may not work on MacOS.

nix-channel --add http://luminescent-dreams-apps.s3-website-us-west-2.amazonaws.com/nixexprs.tar.bz2 luminescent-dreams
nix-channel --update

nix-env -i -A luminescent-dreams.fitnesstrax_0_0_4

FitnessTrax from Source

If you want to build from source, start by cloning the repository:

git clone https://github.com/luminescent-dreams/fitnesstrax.git

You will need to install a variety of GTK development libraries, including Pango, Cairo, and possibly also Atk. Refer to your package manager for more information. You will also need Rust 1.39 (or newer, but I’ve built and tested on 1.39).

Once you have the libraries, you can build the GTK aplication:

cd fitnesstrax/gtk
cargo build --release

You may need to add version numbers to the two relevant Cargo files, as I removed the version numbers on the assumption that all of my distribution would be managed through Nix. I recommend version 0.0.4 for both, and I will fix that problem when I release 0.0.5.

Configuration file and starting the application

Once you have your executable, you’ll need to create a configuration file and then tell FitnessTrax where to find it. You can put it anywhere, but I rather like ~/.fitnesstrax.yaml and may make that the default location some time in the future.

---
series_path: /home/savanni/health/health.series
timezone: America/New_York
language: en

All three fields are required, even though not all three are supported.

Important note about timezone: it must be in the format specified in the Olsen Timezone Database. This will be more familiar than you expect as it is the same list of timezones available when you configure a Mac or an Ubuntu system.

Now, on the command line, this should start the application (replace the configuration file with the path to your configuration file):

CONFIG=~/.fitnesstrax.yaml fitnesstrax

The actual name of the configuration variable will change in version 0.0.5, as will how the configuration file gets handled.

GTK and Rust

Rust has a largely complete GTK binding. However, there are not many good examples of how to build a multi-threaded application with it. GTK is inherently single-threaded, and the Rust bindings enforce that by not providing a Send implementation for any of the widgets. This makes it impossible to move any of the widgets, or even references to those widgets, into secondary threads.

While the team is working on Async/Await support for the bindings, I have no idea what form those will take.

In order to do a multi-threaded application, they have provided a channel that receives messages on the main GTK loop. As normal, you receive both the transmit and receive channels. You must then attach a callback function which will be executed for every message that arrives on the receive channel:

    application.connect_activate(move |app| {
        let (tx, rx) = glib::MainContext::channel(glib::PRIORITY_DEFAULT);

        let ctx = Arc::new(RwLock::new(context::AppContext::new(tx).unwrap()));
        let gui = Arc::new(RwLock::new(components::MainWindow::new(ctx.clone(), app)));

        let gui_clone = gui.clone();
        rx.attach(None, move |msg| {
            gui_clone.write().unwrap().update_from(msg);
            glib::Continue(true)
        });

After this, I have modelled the components that I write pretty heavily on my experience with React. I create a lot of composite components, and as much as I can I try to make them stateless. For instance:

pub fn weight_record_c(record: &WeightRecord) -> gtk::Label {
    gtk::Label::new(Some(&format!("{} kg", &record.weight.value_unsafe)))
}

Or, for something more sophisticated, I may build a struct:

#[derive(Clone)]
pub struct ValidatedTextEntry<A: Clone> {
    pub widget: gtk::Entry,
    value: Arc<RwLock<Result<A, Error>>>,
}

impl<A: Clone> ValidatedTextEntry<A> {
    pub fn new(
        value: A,
        render: Box<dyn Fn(&A) -> String>,
        parse: Box<dyn Fn(&str) -> Result<A, Error>>,
        on_update: Box<dyn Fn(A)>,
    ) -> ValidatedTextEntry<A>
    where
        A: 'static + Clone,
    {
        ...
    }

In this case, the constructor is enough as all of the values get wired together inside the constructor.

This is not React, and there are no declarative automatic by-property updates. So I have to code those myself. This requires some clever thinking about coding. Most components can simply be updated in response to things that are happening internally. For instance, the ValidatedTextEntry above has multiple behaviors attached that respond to how the field changes.

The trickier work involves application-level changes, such as saving data to disk or changing the range of data that should be visible. Aside from the message handler above, some components have an update_from method so that they can update in response to global application events. For instance:

pub struct History {
    pub widget: gtk::Box,
    range_bar: RangeSelector,
    scrolling_history: gtk::ScrolledWindow,
    history_box: gtk::Box,
    ctx: Arc<RwLock<AppContext>>,
}

impl History {
    ...
    pub fn update_from(&self, range: DateRange, history: Vec<Record<TraxRecord>>) {
        ...
    }

}

History is the dominant view of my application, showing every day of information. In response to a ChangeRange{ DateRange, Vec<Record<TraxRecord>> } or a RecordsUpdated{ DateRange, Vec<Record<TraxRecord>> } message, it will delete all of the visible components and create new ones based on the data available. Remarkably, this happens with no visible flicker and is basically instantaneous on the data that most humans will want to look at.

This provides only an overview of GTK + Rust programming. There is a lot more for me to talk about, and I think in future weeks I will take a more detailed dive into the various component architectures that I create, as well as the overall application architecture. For now, consider this a simple teaser. However, this is an open source application, so feel free to take a look at more things in the application.

Looking to Fitnesstrax 0.0.5

For the next version, which I hope will only take a few weeks, I’m going to get configuration working. This means supporting the configuration UI, plus having the rest of the UI translate based on timezone, language, and measurement system.

Some Ramblings for November 24th, 2019

Welcome back to another week of my ramblings.

Autumn here is quite thoroughly over, with Boston having gotten a couple of light freezes and a lot of dreary, watery weather. This picture is from more than a month ago, right around the time I was starting to get sick. Chances are, going on a hike and “taking the fresh mountain air” did not actually help my health at all, seeing as I then spent two weeks sick and several additional weeks with the dregs of illness.

Mental Health

This has been a struggle of a week. My mental health has cratered severly, and I have gotten into the range of not leaving the house because of the risk that I might have to interact with strangers.

Almost all of my trouble is work-related. I despair of ever getting to do work that I like, while I’m simultaneously having to maintain professionalism while dealing with complete disasters, and undisciplined men who are not actually interested in my experience (where by experience, I mean “hey, I’ve done that before and everything I touched exploded into flame”).

It is hard for me to take a mental health day primarily because I know that I will have to return to work to face whatever problems I was unable to solve before. This would not be a huge problem if I actually worked on interesting problems, but instead the problems I face are things like “the state model of this application is uncontrolled and getting worse” or “javascript is flagging errors in places where there cannot possibly errors”.

Let’s get something really clear: My career advanced as far as it did because I was viewed as a man until I reached the 32 years of age. My career has been backsliding ever since, with my options gradually shrinking, even though I have relentlessly expanded my technical skills and my project management skills.

Mental health, and the attendant suicidality that I manage, all revolve around hope. I basically don’t have any.

Obviously, this is not a healthy way to live. Where my co-workers and my clients see how I bring intense passion and skill to my work, the reality is that I actually just cannot disconnect from my work. I have never learned the trick of shutting work out of my mind when I am not working, and so problems at work always spill out into the rest of my life.

Rust, GUIs, and notifications

On my mental health day, I took the time to start getting familiar with building applicatinos in GTK again. I follow a development philosophy in which I have an “application” which is basically a headless object on which all operations can be performed, and the visible GUI is just a thin skin over that.

It turns out, React + Redux applications follow this model almost precisely. We all know that we can store our application state in Redux, using our actions and reducers to update the state, and using React to display the state.

So, on Thursday I took up the challenge of figuring out some version of that with GTK in Rust, and with some help I succeeded.

example main.rs

GTK inherently follows a “push” model of state updates, where React + Redux is made to feel like a pull update. React components exist and will pull any new updates whenever a state update occurs. With GTK, though, I have to build some of that infrastructure myself. I opted to use a listener model, in which I will register an update function for every component that I want to update in response to a state change. That update function receives the updated information and manually updates the component. The state manager (AppContext in the link above) calls all listeners (which really means just executing the registered functions) whenever a state update occurs. Functions that update the state are responsible for ensuring that this dispatch actually happens.

While this is conceptually straightforward, Rust’s very strict memory checking made this rather challenging to figure out.

Pay close attention to the closures on line 73 and 77. connect_clicked requires an Fn, not an FnOnce, implying that the closures will be executed more than once. Less obvious, though, the closures must outlast the closure passed to connect_activate on line 57, because the connect_activate call is specifically for setting up widgets. These widgets will inherently outlast the function call. So, dec_ctx and inc_ctx must both be cloned off of ctx, and ownership of them moved into the closures that get registered as callback functions.

It is not clear to me, however, why dec_ctx and inc_ctx, once moved, outlast an individual invocation of the enclosed functions. I can only assume that both variables (and, technically, ctx in the larger function) and the code fo the closure are bound together into a context that persists much longer than any particular invocation.

In writing this, I also look ta line 67 and wonder about the lifetime of the label, the label clone, and the update listener. In particular, I wonder what happens if the box containing the original counter_label gets destroyed. I suspect that while counter_label itself gets dropped, label_clone and the listener function continue to work as expected, but with no visual effects because no widget actually contians the label_clone.

For my next challenge, I will be converting the AppContext into something that keeps the handle to the underlying trax database, and which observes changes to the currently selecting range of data to show.

Immediate plans include full editing and record creation, plus better formatting. But I still want to get timezone, display units, and translations into the application as quickly as I can.

Games

In the midst of the depression, I have done a lot of self-distraction. First, I bought and played through Sayonara Wild Hearts on the Switch. This is more of a music game, racing through lots of neon scenes at breakneck speeds, and putting together an impression of a story of a broken heart attached to astral highways. But, the game is intensely positive, the protagonist is hella trans, and there are furries, androgynes, and lesbians scattered around as shattered fragments that the protagonist has to reclaim.

Second, today I got Steam working correctly on my NixOS machine and then fired up “Torment: Tides of Numenera”. I bought the game while I was sick last December, but I was unable to play it because so many of the scenes broke on my Macbook’s graphics hardware. I find it horribly ironic that a Linux machine runs games better than a Macbook pro, but that is the world we live in.

As a sidenote, this is what it takes to get Steam working on a NixOS machine. Install these packages:

    unstable.libva
    unstable.steam

Next, enable this in your system’s configuration.nix:

  hardware.opengl.driSupport32Bit = true;

That’s it for this week.

The upcoming week will involve me dedicating most of my spare time to baking until Thanksgiving arrives, and then I will re-approach fitnesstrax late Thursday and for most of the day on Friday.

With any luck, things at work will go well enough that I can stop stressing.

16 Years

Welcome to 2017!

In a few days I move away from Austin, likely never to return. I grew up in Round Rock. I went away for a few years, and then I returned here to begin my career and my adult life right during the dot-com crash. I’m actually a lot older than I look. Many people upon meeting me seem to assume that I’m 27 or so, when in fact I turned 38 late this year. “Wait, how old are you?”

I keenly feel the passage of time. I feel that I have not begun to approach what I wanted to accomplish by now. But, realistically, I have between 40 and 60 years left. The amount of time that I spent here… three more times. And a lot can change in 16 years.

16 years ago, I thought I was a straight man. I was married. I voted for Bush and thought the Republicans could run the country well. I was Catholic and believed the anti-abortion rhetoric, yet I somehow rejected the anti-gay rhetoric. Go figure. Though we knew a few gay men in college, it was shortly after we moved to Austin that my wife and I noticed for the first time pairs of men openly holding hands at formal “respectable” events. We began to feel a relief that this was the kind of safe city that we never really recognized we sought.

15 years ago, my wife and I decided to have a polyamorous relationship. She said that I had suggested it years earlier while we were dating. I did not remember saying that, but it felt like the kind of thing I might have. It was shortly after this, as I thought about love, romance, and relationships, that I began to believe that it was tragic that I was straight and not bisexual. I can remember being apologetic as I (very occasionally) turned a man down. And it was shortly after this that I understood that I was parting ways with the Catholic church… and I did not particularly regret that.

14 years ago, I found out how infidelity felt. Infidelity in a polyamorous relationship looks different than in a monogamous relationship, but it hurts the same. It cuts through hearts, rips out rugs, and crushes dreams.

I also learned that maybe the Republican party was actually made up of a bunch of chronic liars, and became a Democrat. Later I started to understand how violent and hateful Republicans could be. How did I never see this before? And maybe, just maybe, I shouldn’t hold the reproductive health doctrines of men who want to ban abortion but also ban all other forms of contraception and all forms of sex that carry no chance of pregnancy while simultaneously starting a war and lying to me about weapons of mass destruction!

12 years ago I joined a company that became my career for the better part of a decade. They weren’t great… in fact sometimes they were downright awful, but over time my authority became vast, as did my knowledge of everything about the business… except what was in the best interest of the business. Ya know, sometimes we techies need to be informed of the big business direction so we can make decisions intelligently.

10 years ago, with the onset of Saturn Returns, I finally figured out that I was not a man. That moment has lead me through so many changes and to so many of the people that I find so important in my life now. As a man, I would never have made any of the connections I have as an androgyne. This realization sometimes keeps me awake at night, knowing that it is by the grace of but a few words that I have in my life the love that I experience now. More rationally, a few of my current friends would have been my friends anyway, and they would have noticed my egg tendencies, and they would have aided in my hatching. I may have ended up exactly where I am now, on a different schedule.

Letting go of my own gender also let me release my expectations about my sexual orientation. Reparative therapy, especially religious-based “therapy”, is bullshit. We know this. And yet, I successfully “prayed the straight away”!

I also gave up on “til death do we part” and let my marriage end.

Five years ago, I learned photography, and I changed how I see the world. Always watching for that perfect moment. Seeing textures. Analyzing light. Understanding focus and freezing motion. The speckled shadow beneath a canopy. The shimmer of a cobweb five meters up and at least that far away.

Three years ago, I talked myself out of my first suicide attempt. In the aftermath, I evaluated my life. I saw clearly how I was wasting it on my employer’s amazingly small dreams, and I chose to spend some time quite alone. I loved living out in the woods. I hated having to drive for twenty minutes to reach the closest decent internet connection, and for an hour to reach any of my friends. But there is a lot to be said for the peace of the forest, for stars so bright as to light the ground, for rain on the metal roof a mere meter from my lofted bed… and for really cheap rent paid in cash under the table. Oh, and did I mention that my landlady also covered electricty? Pretty epic, especially since the cabin wasn’t well insulated and I had to run 2.5kW of heating that winter.

In the last two years, I have truly started to learn how black lives matter, and how little I understood my own racism in the past. I have learned about social justice, and become keenly aware of my failings. I have gained true confidence in my skills, and become comfortable in my body for the first time in my life. I have felt my socialist/anarchist heart begin to blossom as I notice the Democratic party repeatedly snatch defeat from the jaws of victory.

And, shortly after my birthday in 2014, I met the woman who has become the love of my life. She had to exercise both persistence and patience. I was wounded and avoiding romance, sex, dating. She had to convince me that a lesbian, even a trans-friendly lesbian, could be interested in an androgyne who still had and wasn’t particularly inclined to get rid of eir penis. But, she exercised that persistence, and she waited patiently, while over the course of months I fell in love and I healed. Now we talk of our sixty year plan.

I will miss Austin. I will miss the people here. I will miss all of my bike routes and the restaurants and the events. I will miss the familiarity. And I feel guilt, leaving all of you to stay and stand against the legislature.

But for this woman, where she goes my heart shall follow.

52-Week Photography Challenge

Welcome to 2019!

In this new year, there are several different things that I am trying to do. Not to worry, as I actually started on most of my resolutions on my birthday back in October.

But here is a new one that I’ll be doing in public. The Dogwood 52-Week Photography Challenge.

I will get my first photo published over the weekend, and keep an index of the photos here.