Posted
almost 3 years
ago
A Software Bill of Materials (aka SBoM) is something you’ve probably never heard of, but in future years they’ll hopefully start to become more and more important. In May last year the US president issued an executive order titled Improving the
... [More]
Nation’s Cybersecurity in which it outlines the way that critical software used by various branches of the government should be more traceable and secure. One of the key information captured in a SBoM is “who built what from where” which in open source we’re already familiar with, e.g. “Red Hat built your Linux kernel in a datacenter in the US” rather than “random person from the internet build your container on their laptop using Debian Sarge” and in the former case we also always have the hash of the source archive that was used to build it, and a lot more. Where this concept breaks down is firmware, where lots of different entities build each subsection in different ways, usually due to commercial and technical constraints.
Firmware is often lumped together as one thing, both technically as-in “one download” and conceptually when thinking about OS security. In reality a single firmware image might contain a FSP from Intel, several updated CPU microcode blobs for a few different CPUs, a CSME management engine, an embedded controller update, a UEFI system firmware a lot more. The system firmware is then made up of different file volumes, each with a few dozen EFI “PEI” binaries for initial system start-up and then a couple of hundred (!) “DXE” binaries for things like pre-boot networking and things like fingerprint authentication, mouse and keyboard input.
In the executive order from last May, firmware was explicitly excluded from the list of software that required a SBoM, on the logic that none of the infrastructure or specifications were in place, and it just wasn’t possible to do. Needless to say I’ve been spending the last few months putting all the pieces together to make a firmware SBoM not just possible, but super easy for OEMs, ODMs and IBVs to generate.
The first problem to solve is how to embed the software ID (also known as SWID) metadata into each EFI binary. This is solved by putting coSWID metadata (a DTMF specification) into a new COFF section called, unsurprisingly, “SBOM”. This allows us to automatically capture at build time some data, for instance the tree hash, and the files that were used to build the binary, etc. This is what my friends at Eclypsium have been working on – so soon you can drop a top-level vendor.ini file in your EDK2 checkout with the correct vendor data (legal name, home page etc.) and then you can just build the tree and get everything inserted in this new PE section automatically. This gets us half way there. The uSWID readme explains how to do this manually too, for people not using either the EDK2 build-system or a variant of it.
The second problem is how to include SWID metadata for the blobs we either don’t build, or we can’t modify in any way, e.g. the FSP or uCode. For this there’s an “external” version of the same coSWID metadata which has a simple header we can find in the firmware image. This can either be included in the file volume itself, or just included as a file alongside the binary deliverable. We just have to trust that the vendor includes the correct metadata there – and we’re already trusting the vendor to implement things like SecureBoot correctly. The vendor can either use the [pip install] uswid command line (more examples in the uSWID readme) or more helpfully there’s also a web-generator on the LVFS that can spit out the tiny coSWID blob with the correct header ready to be included somewhere in the binary image.
Open source firmware like coreboot is also in the same boat of course, but here we have more flexibility in how to generate and include the SWID metadata in the image. My friends at Immune and 9elements are planning to work on this really soon, so we can have feature parity for free firmware like coreboot – even when non-free blobs are included into the image so that it can actually work on real hardware.
So, we have the metadata provision from the IBV, ODM and OEM all sprinkled around the update binary. What do we do then? When the binary is uploaded to the LVFS we decompress all the shards of the firmware, and do various checks. At this point we can look for coSWID metadata in the EFI binaries and also uSWID+coSWID metadata for the non-free blobs. From this we can save any of the detected SWID metadata to the per-component datastore, and make it available as a publicly available SBoM HTML page and also .zip archive containing the raw SWID XML data. It probably makes sense to have an external tool, either a CLI utility in the lvfs-website project, or something in native golang — but that doesn’t exist yet.
The vendor also gets the all important “green tick” which means the customer buying the hardware knows that it’s complying with the new requirements. Of course, we can’t check if the ODM has included all the SWID metadata for all the binaries, or included all the SWID components for all of the nonfree chunks, but it’s good enough as a first pass. The next logical thing would be to make a rule saying that the SWID green tick disappears if we detected CPU microcode, but also didn’t detect any microcode SWID metadata, etc. It would also be interesting to show a pie-chart for a given firmware image, showing just where the firmware has been built from, and who by, and how much stuff remains unaccounted for. But, little steps first.
I think I’ve got agreement-in-principal from most of the major stakeholders, and I’ll be hopefully presenting this work alongside AMI to the UEFI forum in a few months time. This means we’re in a position to actually provide SBoM for all firmware when the next EO revision is announced, rather than the ecosystem collapsing into a ball of raw panic.
If you want to add uSWID metadata to your firmware please let me know how I can help, even if it’s not available on the LVFS yet; I think this makes just as much sense for firmware that sits on a USB hub as it does your system firmware. Comments welcome. [Less]
|
Posted
almost 3 years
ago
The command-line tools that are part of GnuTLS (such as certtool and p11tool) had been using the GNU AutoGen for handling command-line arguments. AutoGen (do not be confused with autogen.sh script commonly used in Autotools based projects) does a
... [More]
great job in that regard, as it produces command-line parsing code and the documentation from the single source file. On the other hand, integrating the AutoGen infrastructure into a project can be tricky in many ways, e.g., it requires its own runtime library (libopts) whose interface compatibility is not well maintained. Therefore, we decided to switch to a simpler solution and have finally completed the migration recently. As I spent way too much time on this, I thought it might make sense to summarize the process in case anyone comes into a similar situation.
The first thing we tried was to define the requirements and review the existing alternatives. The requirements turned out to be:
The tool produces code and documentation from the same source, i.e., we do not need to repeat ourselves writing a separate documentation for the commands
The generated code has little to no run-time dependencies
The tool itself doesn’t have exotic (build-)dependencies
We soon realized that there are surprisingly few candidates that meet those requirements. help2man, which is widely used in GNU tools, generates documentation from the command output, while it only supports manual pages (no texinfo/html/pdf support); neither GNU Gengetopt, gaa, nor argtable supports documentation generation at all, etc.
The other thing to consider was how to implement it in a non-disruptive manner. The initial attempt was to combine a help2man-like approach with documentation format conversion using Pandoc, which seemed good in general but the hurdle was that the AutoGen option definitions are written in its own language. Before proceeding with this approach we need to find a way to convert the definitions into the actual option parsing code!
We split this task into two phases: first to parse the AutoGen definitions and convert it to an easier-to-use format such as JSON and YAML, and then process it to generate the code. For the former, I came across pest.rs, which is a PEG (parsing expression grammar) based parser generator with elegantly designed programming interface in Rust. With this I was able to write a converter from the AutoGen definitions to JSON.
Then the generated JSON files are processed by Python scripts to generate the code and documentation. As the first phase is one-shot, we do not need Rust at build time but only need the Python scripts and its dependencies to be integrated in the project.
The scripts and the JSON schema are now hosted as a separate project, which might be useful for other projects. [Less]
|
Posted
almost 3 years
ago
Y’know, all those horrible government forms?
No, I mean the digital ones, meant to be opened specifically with Adobe Reader?
Well, in Q4 2021, Mozilla’s PDF.js landed support for XFA PDF forms, so Firefox is now able to deal with them
... [More]
, which is huge deal, as we have been increasingly encountering such documents over the years, and still will be for a long time, especially given how slow-moving governments can be when it comes to their digital practices.
It would be fantastic to see these code insights put to use in Poppler, the library that Evince, Okular and other applications use… so if someone feels like fixing one of the few biggest issues with reading/filling PDFs under Linux, please use this code (see also: all the XFA-related pull requests) as inspiration to contribute a fix to this and that issue in Poppler!
Of course, there are remaining issues related to forms in PDF.js, but it’s still better than nothing; and perhaps your efforts in replicating this functionality into Poppler can lead to interesting cross-project findings that can also benefit the PDF.js project? [Less]
|
Posted
almost 3 years
ago
We are happy to announce that GNOME has been accepted as a mentor organization for Google Summer of Code 2022!
New contributors will be reaching out in our communication channels for information about the program and to discuss project ideas
... [More]
, please point them to gsoc.gnome.org.
If you have any questions/doubts you can open a topic in our Discourse community, send an email to [email protected], or reach out in our GSoC Matrix chat room. [Less]
|
Posted
almost 3 years
ago
by
[email protected] (Peter Hutterer)
A quick reminder: libei is the library for emulated input. It comes as a pair of C libraries, libei for the client side and libeis for the server side. libei has been sitting mostly untouched since the last status update. There are two use-cases we
... [More]
need to solve for input emulation in Wayland - the ability to emulate input (think xdotool, or Synergy/Barrier/InputLeap client) and the ability to capture input (think Synergy/Barrier/InputLeap server). The latter effectively blocked development in libei [1], until that use-case was sorted there wasn't much point investing too much into libei - after all it may get thrown out as a bad idea. And epiphanies were as elusive like toilet paper and RATs, so nothing much get done. This changed about a week or two ago when the required lightbulb finally arrived, pre-lit from the factory. So, the solution to the input capturing use-case is going to be a so-called "passive context" for libei. In the traditional [2] "active context" approach for libei we have the EIS implementation in the compositor and a client using libei to connect to that. The compositor sets up a seat or more, then some devices within that seat that typically represent the available screens. libei then sends events through these devices, causing input to be appear in the compositor which moves the cursor around. In a typical and simple use-case you'd get a 1920x1080 absolute pointer device and a keyboard with a $layout keymap, libei then sends events to position the cursor and or happily type away on-screen. In the "passive context" approach for libei we have the EIS implementation in the compositor and a client using libei to connect to that. The compositor sets up a seat or more, then some devices within that seat that typically represent the physical devices connected to the host computer. libei then receives events from these devices, causing input to be generated in the libei client. In a typical and simple use-case you'd get a relative pointer device and a keyboard device with a $layout keymap, the compositor then sends events matching the relative input of the connected mouse or touchpad. The two notable differences are thus: events flow from EIS to libei and the devices don't represent the screen but rather the physical [3] input devices. This changes libei from a library for emulated input to an input event transport layer between two processes. On a much higher level than e.g. evdev or HID and with more contextual information (seats, devices are logically abstracted, etc.). And of course, the EIS implementation is always in control of the events, regardless which direction they flow. A compositor can implement an event filter or designate key to break the connection to the libei client. In pseudocode, the compositor's input event processing function will look like this: function handle_input_events(): real_events = libinput.get_events() for e in real_events: if input_capture_active: send_event_to_passive_libei_client(e) else: process_event(e) emulated_events = eis.get_events_from_active_clients() for e in emulated_events: process_event(e) Not shown here are the various appropriate filters and conversions in between (e.g. all relative events from libinput devices would likely be sent through the single relative device exposed on the EIS context). Again, the compositor is in control so it would be trivial to implement e.g. capturing of the touchpad only but not the mouse. In the current design, a libei context can only be active or passive, not both. The EIS context is both, it's up to the implementation to disconnect active or passive clients if it doesn't support those. Notably, the above only caters for the transport of input events, it doesn't actually make any decision on when to capture events. This handled by the CaptureInput XDG Desktop Portal [4]. The idea here is that an application like Synergy/Barrier/InputLeap server connects to the CaptureInput portal and requests a CaptureInput session. In that session it can define pointer barriers (left edge, right edge, etc.) and, in the future, maybe other triggers. In return it gets a libei socket that it can initialize a libei context from. When the compositor decides that the pointer barrier has been crossed, it re-routes the input events through the EIS context so they pop out in the application. Synergy/Barrier/InputLeap then converts that to the global position, passes it to the right remote Synergy/Barrier/InputLeap client and replays it there through an active libei context where it feeds into the local compositor. Because the management of when to capture input is handled by the portal and the respective backends, it can be natively integrated into the UI. Because the actual input events are a direct flow between compositor and application, the latency should be minimal. Because it's a high-level event library, you don't need to care about hardware-specific details (unlike, say, the inputfd proposal from 2017). Because the negotiation of when to capture input is through the portal, the application itself can run inside a sandbox. And because libei only handles the transport layer, compositors that don't want to support sandboxes can set up their own negotiation protocol. So overall, right now this seems like a workable solution. [1] "blocked" is probably overstating it a bit but no-one else tried to push it forward, so.. [2] "traditional" is probably overstating it for a project that's barely out of alpha development [3] "physical" is probably overstating it since it's likely to be a logical representation of the types of inputs, e.g. one relative device for all mice/touchpads/trackpoints [4] "handled by" is probably overstating it since at the time of writing the portal is merely a draft of an XML file [Less]
|
Posted
almost 3 years
ago
Cockpit CI demands Testing Cockpit is not an easy task – each pull request gets tested by over 300 browser integration test cases on a dozen operating systems. Each per-OS test suite starts hundreds of virtual machines, and many of them exercise
... [More]
them quite hard: provoking crashes, rebooting, attaching storage or network devices, or changing boot loader arguments.
With these requirements we absolutely depend on a working /dev/kvm in the test environment, and a performant host to run all these tests in a reasonable time. [Less]
|
Posted
almost 3 years
ago
Update on what happened across the GNOME project in the week from February 25 to March 04.
Miscellaneous
Sophie Herold reports
Chris 🌱️ and I launched the Update App Screenshots Initiative. Our short-term goal is to have up-to-date screenshots for
... [More]
all Core apps for the upcoming GNOME 42 release. So far, for 15 of 30 Core apps, merge requests are created or already merged.
If you are maintaining or contributing to an app, you can have a look at our screenshot guidelines, and if needed update your screenshots accordingly.
Third Party Projects
Bilal Elmoussaoui announces
flatpak-vscode 0.0.21 is out with support of
New Flatpak manifest selector
Watch for Flatpak manifests changes and modify state accordingly
Support JSON manifests with comments
Support --require-version
Better state management
Felix announces
I have released an updated version of “Audio Sharing”. The interface has been adapted to the new Adwaita design, and some bugs that prevented streaming have been fixed.
In case you don’t know it yet - with this small tool you can stream the audio playback from your computer to other devices in your local network.
You can find a more detailed description on the project homepage. It is available for download on Flathub.
sonnyp says
The first release of Workbench is out
Workbench is
A sandbox to learn and experiment with GNOME technologies
A developer tool for testing and building with an instant feedback loop
https://flathub.org/apps/details/re.sonny.Workbench
Documentation
Emmanuele Bassi announces
The Getting Started tutorial for newcomers to GNOME application development is now complete. You can follow it to learn how to use GNOME Builder to write your own GNOME application; loading and saving content with asynchronous operations; changing the style of your application; adding menus; and saving preferences. The documentation is ready for GNOME 42 and libadwaita: https://developer.gnome.org/documentation/tutorials/beginners/getting_started.html
GNOME Shell Extensions
Simon Schneegans says
The Desktop-Cube extension for GNOME Shell has been updated and brings many new features! Most importantly, you can now freely rotate the cube by click-and-drag. This works in the overview, on the desktop, and on the panel. The latter is especially cool if you have a maximized window!
Other changes include:
Support for GNOME 42.
Proper touch-screen support.
Support for online-translations.
You can watch a trailer on YouTube!
That’s all for this week!
See you next week, and be sure to stop by #thisweek:gnome.org with updates on your own projects! [Less]
|
Posted
almost 3 years
ago
Yes, ladies, gentlemen, and seemingly-dead plants, it’s happening: after over 10 months of incremental work from the community, we are now releasing version 0.6 of our favorite personal productivity app. This release comes with some new features
... [More]
, lots of code improvements, many bugfixes and UX refinements (I am told that the “Better procrastination button”, presented below, deserves a place in the Museum of Modern Art).
Save the children, and the parents… tasks.
GTG 0.6 includes fixes for a long-standing higgs-bugson crasher, that would happen under some unpredictable conditions (such as issue 595 and issue 724) and was therefore hard to reproduce for a long time… until I hit the point, in my chaotic 2021 year, where I had accumulated over 2500 tasks as I forgot to clean my closed tasks for a few months… when your data file is that big, the bug becomes much easier to trigger.
We also fixed this mandelbug that would make GTG show inconsistent results in the list of tasks, under some circumstances. Neui was able to deduce the cause for the problem by looking at the tracebacks, and provided a fix in liblarch. GTG 0.6 will therefore require an updated version of liblarch.
Those two deeply nested bugs are the reason why I’m officially codenaming this release… “Shin Kidō Senki GTG Wing: Endless Recursion”.
(Insert a Zimmer-style “bwaaah” brass sound effect here. An Auralnaut is fine too.)
“Where’s my cal, Dave?”
Hey, we have a new synchronization backend now! If you have a CalDAV server (for example, something with OwnCloud or YUNOHOST), you can use this to synchronize GTG across your multiple computers.
It’s been a long time in the works. I would like to thank Mildred for doing the initial research and coding, then François Schmidts for doing another attempt at coding this feature, and for being very patient with us until we could finally merge this, after a lot of other architectural work landed. I know it can sometimes be tough for new contributors to wait for their code to land in an established open-source project, and for that project to also release the code in a stable release.
(With apologies to Mildred, François, and Padmé.)
Check out the built-in user manual pages to learn how you can use the CalDAV sync feature. There’s an online copy of the user manual, too. So far nobody reported catastrophic failures, so this sync backend seems to be Enterprise Ready™, but if you do encounter issues related to the CalDAV backend, kindly report them (even better if you can help with fixes and refinements!)
Gamification plugin
Sometimes, a little extra playful motivation can help you go through your day. Part of the brainstorming for gamification and virtual assistant features, Mohieddine Drissi created an initial plugin to add some game-like elements to GTG:
Here too, apologies to Mohieddine and Padmé!
Please try out this new feature, and look at the ticket linked above. Do you see additional features that would be good to add? Should they be part of this plugin, or a separate plugin? Let us know.
Modernized tag editor
This is what it used to look like:
This is what it looks like now:
Better procrastination button
One of the most important buttons in my GTG workflow is the “Do it tomorrow” button, and its associated menubutton that lets you reschedule a task’s start date a couple of days into the future. I call that feature the “procrastination” button and this might sound a bit silly, but it really is just an essential way to manage a frequently-changing set of priorities, schedule and obligations, and a way to manage your energy levels.
This release improves this feature with some additional attention to detail:
Inspired by some initial work by Laurent Combe, First, “Neui” made some pretty impressive hackery to make this an adaptive button that would automatically change its label depending on the available window/screen width, which now makes GTG capable of fitting within a split-screen on smaller screen resolutions. He did this only with Python code within GTG, so in case you’re wondering, GTG is not dependent on libadwaita for this to function.
In issue #550, I detailed the various inconsistencies between this menubutton and the contextual (right-click) menus for deferring tasks. As we were nearing the 0.6 release, I had a “How Hard Can It Be, Really?™” moment and went through a late night coding session to scratch at least part of my design itch, as it had been annoying me for a year by now and I didn’t want to stare at it for another release cycle. So this pull request of mine solves at least one half of the problem, which is probably better than nothing. Anyone is welcome to finish the 2nd half; you might have to harmonize that code with dates.py. Until then, here’s how it looks like now:
Errors will be noticed easily again
GTG used to have a fantastically avant-garde technological feature where it would automatically catch and display Python errors (tracebacks) in the graphical user interface. This feature got lost in what led up to the 0.4 release, but it is now making a comeback in GTG 0.6, thanks to Neui’s fine engineering work. It not only catches tracebacks, but also determines whether they are critical or if the application can possibly continue running. I did some UI & UX refinements on top of Neui’s version, and we now have this honest but still reasonably reassuring dialog:
If you click the expander, you get the traceback, along with some additional information, all neatly MarkDown-formatted so that it can be readily pasted alongside your bug report on modern bug trackers:
Of course, our software is perfect and you should never actually encounter such errors/uncaught exceptions, but… you never know. In the rare cases where this might happen, as a user, it’s better to be made aware if such a problem occurs—and the possibility of the application’s internal state being inconsistent—right when you trigger the issue, so that you can know what sequence of events led to a problem in the code, and report it in our issue tracker.
If you need to test this dialog (ex.: to test translations) but can’t find bugs in our flawless code, you can use GTG’s built-in “Developer Console” to trigger a traceback that will make that dialog appear (see this tip in our documentation).
Tough times, strong community
It’s been a hell of a year, but it’s still been less than a year since 0.5. Ideally we would be on a faster cycle, but we kept merging new and interesting things, and 2021 was pretty intense for many of us, so we slipped a bit.
Diego had to take time off to take care of his family and personal health, and I, certainly like many readers here, had a pretty busy year in my day-to-day work and beyond.
I deeply appreciate the GTG community’s patience in contributing, getting involved and sticking together to improve this project. Personally, I was not able to keep up very closely with all the activity going on; I received somewhere around 1900 emails related to GTG tickets and merge requests during the 0.6 cycle (from April 6th 2021 to this day), so it is clear that the community’s involvement is really strong here, and that’s awesome. If you’re not already part of this, consider joining the fun!
Let me take a minute to dish out some praise here for the fabulous and tireless work of our most frequent contributors. Thank you,
“Neui” for contributing a ton of patches, code reviews, advice, that helped the project’s technical architecture progress steadily throughout the year (also, thank you for investigating some of my craziest bugs and providing solutions for them!) ;
Diego, for plowing through architectural code refactoring, bugfixing, code reviews, during what I know was a difficult time;
Mildred and François, for making the CalDAV sync backend possible.
Mohieddine Drissi for creating the gamification plugin
Danielle Vansia, who not only updated and expanded the user manual so that you could know how the hell to use that CalDAV feature, but also kindly took Diego’s “changes since 0.5” braindump, expanded and polished it into the release notes you can find further below.
…and many other contributors who have made this release possible by providing bug fixes, code quality improvements, etc. They are listed in the about dialog’s credit section for this release, too 😉
Releasing because I can’t stand 0.5 anymore
You know, my gtg_data.xml file is pretty heavy:
No, I mean really heavy:
(Insert Hans Zimmer brass sounds)
As I write this, my data file contains over 2700 tasks (~1800 open, ~700 done, ~ 200 dismissed). The problem is, when you reach that kind of heaviness in your data file, in 0.5 you will encounter not only occasional crashes “when adding a parent/child task or when marking a recurrent task as done” (as mentioned at the beginning of this blog post), but also when trying to delete a bunch of tasks at once… which meant my “purge tasks” feature was not working anymore, which meant I kept piling on hundreds of “closed” tasks every month that I couldn’t easily remove from my tasks data file, which meant performance kept getting worse and crashes were getting more likely…
These are the total number of tasks in my XML file over time (including open, done and dismissed tasks). Each dip is when I tell GTG to purge closed tasks from the file. I know, those numbers keep going back up, higher and higher; I really need to find people to take care of some of my home and infrastructure bullshit, but that’s besides the point 😉
With those issues exacerbated by my “abnormally heavy” data file growing heavier every day, you can imagine that version 0.5 had become unbearable for me in my day-to-day use…
…therefore we have a perfect excuse to release version 0.6, which solves those issues! 😇 “Release when you can’t stand the previous release anymore!”
So here we are, GTG 0.6 comes right on time for income tax season! (and if it does help you make it through that period alive, maybe you can deduct some massive donations to Diego via LiberaPay or via GumRoad 😉
Release notes
Thanks to Danielle, Diego and Neui for taking the time to research & detail the noteworthy changes to create the NEWS file, which you can read below.
Show release notes (585 more words)
New Features:
A new CalDAV backend is available, and the backends dialog is available again. CalDAV is a calendaring protocol that allows a client to access items from a server. GTG now provides support for this standard, and with this new sync service, you can manage all your tasks in one place.
The new “Gamify” plugin adds a game aspect to your GTG workflow, such as the ability to set task targets and task completion streaks.
The Tag Editor was completely redesigned.
Added support for undo/redo actions in the Task Editor.
Added the ability to collapse and expand all tasks in the main menu.
Added the F10 shortcut to open the main menu.
ESC now closes the calendar picker window.
Added the CTRL+B shortcut to set focus on the sidebar.
Added an option to set the due date to “today” in the context menu.
The “Mark as not done” and “Undismiss” actions were replaced by a unified “Reopen” action for closed tasks in the right-click context menu.
The “Start Tomorrow” button is now adaptive, so that the main window can be resized to smaller widths.
The task deferral menubutton (next to “Start Tomorrow”) now shows both weekday names and the offset number of days from today. It also allows easily deferring to 7 days instead of only 6 days, and the ordering of this menubutton matches better with other menus.
Backend, Code Quality, and Performance Improvements:
Reintroduced the global exception/traceback catcher, with a dialog showing up when an error occurs.
Made an improvement to avoid infinite loops when entering invalid dates in the Quick Add entry.
Made an update to prevent errors when no task is selected.
Removed some deprecation warnings.
Added StartupWMClass to make pinning on KDE work.
Used application ID as the window icon (so KDE shows the correct icon).
Made several changes in preparation for Gtk 4.0, as well as updated a number of deprecated GTK-related items.
Made a ton of PEP8 and style fixes.
Refactored the date class.
Updated the ability to render tag icons better on HiDPI.
Updated the anonymize script.
Added gtg://TASK-ID to the command-line help.
Added the -p parameter for profiling in debug.sh.
We migrated from “Nose” to “PyTest” for the test suite, as Nose is unmaintained.
Bug Fixes:
Fixed possible crash when trying to create parent task, and similar operations, when the “Open” tab has not been opened yet (maximum recursion depth error).
Made a change to save a task before creating a parent. This prevents an error that appeared when a task title would be reset after adding a parent.
Fixed an issue where tags were being duplicated.
Fixed an issue where tags and saved searches with the same name were being considered duplicates.
Fixed a bug where every editor window would come back if GTG wasn’t closed cleanly (i.e., shut down).
Made several fixes for scripts.
Fixed certain main menu entries not being selectable via the keyboard.
Fixed the cut-off when you expand the columns too much.
Fixed a regression where symbols in tags (e.g., dashes and dots) were not recognized by the Task Editor. Also, added support for a few more.
Fixed a bug where tags’ icon, parent, and color were not being removed in the XML file.
Fixed a bug that occured when GTG just starts up with no tasks selected, and the user tried to use the “Add Parent” hotkey.
Avoid blurry tag color rounded rectangles on non-HiDPI screens.
Documentation Updates:
Added documentation to the Contributor Docs for contributing to the User Manual (i.e., for writing help files).
Added information about using flamegraph for profiling GTG for performance documentation (in the Contributor Docs).
Updated the user manual for this release.
For more details on all of these new features, improvements, and fixes, see the 0.6 release’s milestone.
Time to "flatpak update", folks:
[Less]
|
Posted
almost 3 years
ago
Yes, ladies, gentlemen, and seemingly-dead plants, it’s happening: after over 10 months of incremental work from the community, we are now releasing version 0.6 of our favorite personal productivity app. This release comes with some new features
... [More]
, lots of code improvements, many bugfixes and UX refinements (I am told that the “Better procrastination button”, presented below, deserves a place in the Museum of Modern Art).
Save the children, and the parents… tasks.
GTG 0.6 includes fixes for a long-standing higgs-bugson crasher, that would happen under some unpredictable conditions (such as issue 595 and issue 724) and was therefore hard to reproduce for a long time… until I hit the point, in my chaotic 2021 year, where I had accumulated over 2500 tasks as I forgot to clean my closed tasks for a few months… when your data file is that big, the bug becomes much easier to trigger.
We also fixed this mandelbug that would make GTG show inconsistent results in the list of tasks, under some circumstances. Neui was able to deduce the cause for the problem by looking at the tracebacks, and provided a fix in liblarch. GTG 0.6 will therefore require an updated version of liblarch.
Those two deeply nested bugs are the reason why I’m officially codenaming this release… “Shin Kidō Senki GTG Wing: Endless Recursion”.
(Insert a Zimmer-style “bwaaah” brass sound effect here. An Auralnaut is fine too.)
“Where’s my cal, Dave?”
Hey, we have a new synchronization backend now! If you have a CalDAV server (for example, something with OwnCloud or YUNOHOST), you can use this to synchronize GTG across your multiple computers.
It’s been a long time in the works. I would like to thank Mildred for doing the initial research and coding, then François Schmidts for doing another attempt at coding this feature, and for being very patient with us until we could finally merge this, after a lot of other architectural work landed. I know it can sometimes be tough for new contributors to wait for their code to land in an established open-source project, and for that project to also release the code in a stable release.
(With apologies to Mildred, François, and Padmé.)
Check out the built-in user manual pages to learn how you can use the CalDAV sync feature. There’s an online copy of the user manual, too. So far nobody reported catastrophic failures, so this sync backend seems to be Enterprise Ready™, but if you do encounter issues related to the CalDAV backend, kindly report them (even better if you can help with fixes and refinements!)
Gamification plugin
Sometimes, a little extra playful motivation can help you go through your day. Part of the brainstorming for gamification and virtual assistant features, Mohieddine Drissi created an initial plugin to add some game-like elements to GTG:
Here too, apologies to Mohieddine and Padmé!
Please try out this new feature, and look at the ticket linked above. Do you see additional features that would be good to add? Should they be part of this plugin, or a separate plugin? Let us know.
Modernized tag editor
This is what it used to look like:
This is what it looks like now:
Better procrastination button
One of the most important buttons in my GTG workflow is the “Do it tomorrow” button, and its associated menubutton that lets you reschedule a task’s start date a couple of days into the future. I call that feature the “procrastination” button and this might sound a bit silly, but it really is just an essential way to manage a frequently-changing set of priorities, schedule and obligations, and a way to manage your energy levels.
This release improves this feature with some additional attention to detail:
Inspired by some initial work by Laurent Combe, First, “Neui” made some pretty impressive hackery to make this an adaptive button that would automatically change its label depending on the available window/screen width, which now makes GTG capable of fitting within a split-screen on smaller screen resolutions. He did this only with Python code within GTG, so in case you’re wondering, GTG is not dependent on libadwaita for this to function.
In issue #550, I detailed the various inconsistencies between this menubutton and the contextual (right-click) menus for deferring tasks. As we were nearing the 0.6 release, I had a “How Hard Can It Be, Really?™” moment and went through a late night coding session to scratch at least part of my design itch, as it had been annoying me for a year by now and I didn’t want to stare at it for another release cycle. So this pull request of mine solves at least one half of the problem, which is probably better than nothing. Anyone is welcome to finish the 2nd half; you might have to harmonize that code with dates.py. Until then, here’s how it looks like now:
Errors will be noticed easily again
GTG used to have a fantastically avant-garde technological feature where it would automatically catch and display Python errors (tracebacks) in the graphical user interface. This feature got lost in what led up to the 0.4 release, but it is now making a comeback in GTG 0.6, thanks to Neui’s fine engineering work. It not only catches tracebacks, but also determines whether they are critical or if the application can possibly continue running. I did some UI & UX refinements on top of Neui’s version, and we now have this honest but still reasonably reassuring dialog:
If you click the expander, you get the traceback, along with some additional information, all neatly MarkDown-formatted so that it can be readily pasted alongside your bug report on modern bug trackers:
Of course, our software is perfect and you should never actually encounter such errors/uncaught exceptions, but… you never know. In the rare cases where this might happen, as a user, it’s better to be made aware if such a problem occurs—and the possibility of the application’s internal state being inconsistent—right when you trigger the issue, so that you can know what sequence of events led to a problem in the code, and report it in our issue tracker.
If you need to test this dialog (ex.: to test translations) but can’t find bugs in our flawless code, you can use GTG’s built-in “Developer Console” to trigger a traceback that will make that dialog appear (see this tip in our documentation).
Tough times, strong community
It’s been a hell of a year, but it’s still been less than a year since 0.5. Ideally we would be on a faster cycle, but we kept merging new and interesting things, and 2021 was pretty intense for many of us, so we slipped a bit.
Diego had to take time off to take care of his family and personal health, and I, certainly like many readers here, had a pretty busy year in my day-to-day work and beyond.
I deeply appreciate the GTG community’s patience in contributing, getting involved and sticking together to improve this project. Personally, I was not able to keep up very closely with all the activity going on; I received somewhere around 1900 emails related to GTG tickets and merge requests during the 0.6 cycle (from April 6th 2021 to this day), so it is clear that the community’s involvement is really strong here, and that’s awesome. If you’re not already part of this, consider joining the fun!
Let me take a minute to dish out some praise here for the fabulous and tireless work of our most frequent contributors. Thank you,
“Neui” for contributing a ton of patches, code reviews, advice, that helped the project’s technical architecture progress steadily throughout the year (also, thank you for investigating some of my craziest bugs and providing solutions for them!) ;
Diego, for plowing through architectural code refactoring, bugfixing, code reviews, during what I know was a difficult time;
Mildred and François, for making the CalDAV sync backend possible.
Mohieddine Drissi for creating the gamification plugin
Danielle Vansia, who not only updated and expanded the user manual so that you could know how the hell to use that CalDAV feature, but also kindly took Diego’s “changes since 0.5” braindump, expanded and polished it into the release notes you can find further below.
…and many other contributors who have made this release possible by providing bug fixes, code quality improvements, etc. They are listed in the about dialog’s credit section for this release, too 😉
Releasing because I can’t stand 0.5 anymore
You know, my gtg_data.xml file is pretty heavy:
No, I mean really heavy:
(Insert Hans Zimmer brass sounds)
As I write this, my data file contains over 2700 tasks (~1800 open, ~700 done, ~ 200 dismissed). The problem is, when you reach that kind of heaviness in your data file, in 0.5 you will encounter not only occasional crashes “when adding a parent/child task or when marking a recurrent task as done” (as mentioned at the beginning of this blog post), but also when trying to delete a bunch of tasks at once… which meant my “purge tasks” feature was not working anymore, which meant I kept piling on hundreds of “closed” tasks every month that I couldn’t easily remove from my tasks data file, which meant performance kept getting worse and crashes were getting more likely…
These are the total number of tasks in my XML file over time (including open, done and dismissed tasks). Each dip is when I tell GTG to purge closed tasks from the file. I know, those numbers keep going back up, higher and higher; I really need to find people to take care of some of my home and infrastructure bullshit, but that’s besides the point 😉
With those issues exacerbated by my “abnormally heavy” data file growing heavier every day, you can imagine that version 0.5 had become unbearable for me in my day-to-day use…
…therefore we have a perfect excuse to release version 0.6, which solves those issues! 😇 “Release when you can’t stand the previous release anymore!”
So here we are, GTG 0.6 comes right on time for income tax season! (and if it does help you make it through that period alive, maybe you can deduct some massive donations to Diego via LiberaPay or via GumRoad 😉 especially considering the metric crapton of refactoring work that will need to be accomplished for 0.7…
Release notes
Thanks to Danielle, Diego and Neui for taking the time to research & detail the noteworthy changes to create the NEWS file, which you can read below.
Show release notes (585 more words)
New Features:
A new CalDAV backend is available, and the backends dialog is available again. CalDAV is a calendaring protocol that allows a client to access items from a server. GTG now provides support for this standard, and with this new sync service, you can manage all your tasks in one place.
The new “Gamify” plugin adds a game aspect to your GTG workflow, such as the ability to set task targets and task completion streaks.
The Tag Editor was completely redesigned.
Added support for undo/redo actions in the Task Editor.
Added the ability to collapse and expand all tasks in the main menu.
Added the F10 shortcut to open the main menu.
ESC now closes the calendar picker window.
Added the CTRL+B shortcut to set focus on the sidebar.
Added an option to set the due date to “today” in the context menu.
The “Mark as not done” and “Undismiss” actions were replaced by a unified “Reopen” action for closed tasks in the right-click context menu.
The “Start Tomorrow” button is now adaptive, so that the main window can be resized to smaller widths.
The task deferral menubutton (next to “Start Tomorrow”) now shows both weekday names and the offset number of days from today. It also allows easily deferring to 7 days instead of only 6 days, and the ordering of this menubutton matches better with other menus.
Backend, Code Quality, and Performance Improvements:
Reintroduced the global exception/traceback catcher, with a dialog showing up when an error occurs.
Made an improvement to avoid infinite loops when entering invalid dates in the Quick Add entry.
Made an update to prevent errors when no task is selected.
Removed some deprecation warnings.
Added StartupWMClass to make pinning on KDE work.
Used application ID as the window icon (so KDE shows the correct icon).
Made several changes in preparation for Gtk 4.0, as well as updated a number of deprecated GTK-related items.
Made a ton of PEP8 and style fixes.
Refactored the date class.
Updated the ability to render tag icons better on HiDPI.
Updated the anonymize script.
Added gtg://TASK-ID to the command-line help.
Added the -p parameter for profiling in debug.sh.
We migrated from “Nose” to “PyTest” for the test suite, as Nose is unmaintained.
Bug Fixes:
Fixed possible crash when trying to create parent task, and similar operations, when the “Open” tab has not been opened yet (maximum recursion depth error).
Made a change to save a task before creating a parent. This prevents an error that appeared when a task title would be reset after adding a parent.
Fixed an issue where tags were being duplicated.
Fixed an issue where tags and saved searches with the same name were being considered duplicates.
Fixed a bug where every editor window would come back if GTG wasn’t closed cleanly (i.e., shut down).
Made several fixes for scripts.
Fixed certain main menu entries not being selectable via the keyboard.
Fixed the cut-off when you expand the columns too much.
Fixed a regression where symbols in tags (e.g., dashes and dots) were not recognized by the Task Editor. Also, added support for a few more.
Fixed a bug where tags’ icon, parent, and color were not being removed in the XML file.
Fixed a bug that occured when GTG just starts up with no tasks selected, and the user tried to use the “Add Parent” hotkey.
Avoid blurry tag color rounded rectangles on non-HiDPI screens.
Documentation Updates:
Added documentation to the Contributor Docs for contributing to the User Manual (i.e., for writing help files).
Added information about using flamegraph for profiling GTG for performance documentation (in the Contributor Docs).
Updated the user manual for this release.
For more details on all of these new features, improvements, and fixes, see the 0.6 release’s milestone.
Time to "flatpak update", folks:
[Less]
|
Posted
almost 3 years
ago
Via Zach Holman’s blog post I found an interesting Twitter discussion that kicked off with these questions:
A couple of tough questions for all of you:1. Is the date 2022-06-01 equal to the time 2022-06-01 12:00:00?2. Is the date 2022-06-01
... [More]
between the time 2022-06-01 12:00:00 and the time 2022-12-31 12:00:00?3. Is the time 2022-06-01 12:00:00 after the date 2022-06-01?
I’ve been involved for two years and counting1 in the design of Temporal, an enhancement for the JavaScript language that adds modern facilities for handling dates and times. One of the principles of Temporal that was established long before I got involved, is that we should use different objects to represent different concepts. For example, if you want to represent a calendar date that’s not associated with any specific time of day, you use a class that doesn’t require you to make up a bogus time of day.2 Each class has a definition for equality, comparison, and other operations that are appropriate to the concept it represents, and you get to specify which one is appropriate for your use case by your choice of which one you use. In other, more jargony, words, Temporal offers different data types with different semantics.3
For me these questions all boil down to, when we consider a textual representation like 2022-06-01, what concept does it represent? I would say that each of these strings can represent more than one concept, and to get a good answer, you need to specify which concept you are talking about.
So, my answers to the three questions are “it depends”, “no but maybe yes”, and “it depends.” I’ll walk through why I think this, and how I would solve it with Temporal, for each question.
You can follow along or try out your own answers by going the Temporal documentation page, and opening your browser console. That will give you an environment where you can try these examples and experiment for yourself.
Question 1
Is the date 2022-06-01 equal to the time 2022-06-01 12:00:00?
As I mentioned above, Temporal has different data types with different semantics. In the case of this question, what the question refers to as a “time” we call a “date-time” in Temporal4, and the “date” is still a date. The specific types we’d use are PlainDateTime and PlainDate, respectively. PlainDate is a calendar date that doesn’t have a time associated with it: a single square on a wall calendar. PlainDateTime is a calendar date with a wall-clock time. In both cases, “plain” refers to not having a time zone attached, so we know we’re not dealing with any 23-hour or 25-hour or even more unusual day lengths.
The reason I say that the answer depends, is that you simply can’t say whether a date is equal to a date-time. They are two different concepts, so the answer is not well-defined. If you want to do that, you have to convert one to the other so that you either compare two dates, or two date-times, each with their accompanying definition of equality.
You do this in Temporal by choosing the type of object to create, PlainDate or PlainDateTime, and the resulting object’s equals() method will do the right thing:
> Temporal.PlainDate.from('2022-06-01').equals('2022-06-01 12:00:00')
true
> Temporal.PlainDateTime.from('2022-06-01').equals('2022-06-01 12:00:00')
false
I think either of PlainDate or PlainDateTime semantics could be valid based on your application, so it seems important that both are within reach of the programmer. I will say that I don’t expect PlainDateTime will get used very often in practice.5 But I can think of a use case for either one of these:
If you have a list of PlainDateTime events to present to a user, and you want to filter them by date. Let’s say we have data from a pedometer, where we care about what local time it was in the user’s time zone when they got their exercise, and the user has asked to see all the exercise they got yesterday. In this case I’d use date semantics: convert the PlainDateTime data to PlainDate data.
On the other hand, if the 2022-06-01 input comes from a date picker widget where the user could have input a time but didn’t, then we might decide that it makes sense to default the time of day to midnight, and therefore use date-time semantics.
Question 2
Is the date 2022-06-01 between the time 2022-06-01 12:00:00 and the time 2022-12-31 12:00:00?
I think the answer to this one is more unambiguously a no. If we use date-time semantics (in Temporal, PlainDateTime.compare()) the date implicitly converts to midnight on that day, so it comes before both of the date-times. If we use date semantics (PlainDate.compare()), 2022-06-01 and 2022-06-01 12:00:00 are equal as we determined in Question 1, so I wouldn’t say it’s “between” the two date-times.
> Temporal.PlainDateTime.compare('2022-06-01', '2022-06-01 12:00:00')
-1
> Temporal.PlainDateTime.compare('2022-06-01', '2022-12-31 12:00:00')
-1
> Temporal.PlainDate.compare('2022-06-01', '2022-06-01 12:00:00')
0
> Temporal.PlainDate.compare('2022-06-01', '2022-12-31 12:00:00')
-1
(Why these numbers?6 The compare methods return −1, 0, or 1, according to the convention used by Array.prototype.sort, so that you can do things like arr.sort(Temporal.PlainDate.compare). 0 means the arguments are equal and −1 means the first comes before the second.)
But maybe the answer still depends a little bit on what your definition of “between” is. If it means the date-times form a closed interval instead of an open interval, and we are using date semantics, then the answer is yes.7
Question 3
Is the time 2022-06-01 12:00:00 after the date 2022-06-01?
After thinking about the previous two questions, this should be clear. If we’re using date semantics, the two are equal, so no. If we’re using date-time semantics, and we choose to convert a date to a date-time by assuming midnight as the time, then yes.
Other people’s answers
I saw a lot of answers saying that you need more context to be able to compare the two, so I estimate that the way Temporal requires that you give that context, instead of assuming one or the other, does fit with the way that many people think. However, that wasn’t the only kind of reply I saw. (Otherwise the discussion wouldn’t have been that interesting!) I’ll discuss some of the other common replies that I saw in the Twitter thread.
“Yes, no, no: truncate to just the dates and compare those, since that’s the data you have in common.” People who said this seem like they might naturally gravitate towards date semantics. I’d estimate that date semantics are probably correct for more use cases. But maybe not your use case!
“No, no, yes: a date with no time means midnight is implicit.” People who said this seem like they might naturally gravitate towards date-time semantics. It makes sense to me that programmers think this way; if you’re missing a piece of data, just fill in 0 and keep going. I’d estimate that this isn’t how a lot of nontechnical users think of dates, though.
In this whole post I’ve assumed we assume the time is midnight when we convert a date to a date-time, but in the messy world of dates and times, it can make sense to assume other times than midnight, as well. This comes up especially if time zones are involved. For example, you might assume noon, or start-of-day, instead. Start-of-day is often, but not always midnight:
Temporal.PlainDateTime.from('2018-11-04T12:00')
.toZonedDateTime('America/Sao_Paulo')
.startOfDay()
.toPlainTime() // -> 01:00
“These need to have time zones attached for the question to make sense.” If this is your first reaction when you see a question like this, great! If you write JavaScript code, you probably make fewer bugs just by being aware that JavaScript’s Date object makes it really easy to confuse time zones.
I estimate that Temporal’s ZonedDateTime type is going to fit more use cases in practice than either PlainDate or PlainDateTime. In that sense, if you find yourself with this data and these questions in your code, it makes perfect sense to ask yourself whether you should be using a time-zone-aware type instead. But, I think I’ve given some evidence above that sometimes the answer to that is no: for example, the pedometer data that I mentioned above.
“Dates without times are 24-hour intervals.” Also mentioned as “all-day events”. I can sort of see where this comes from, but I’m not sure I agree with it. In the world where JavaScript Date is the only tool you have, it probably makes sense to think of a date as an interval. But I’d estimate that a lot of non-programmers don’t think of dates this way: instead, it’s a square on your calendar!
It’s also worth noting that in some calendar software, you can create an all-day event that lasts from 00:00 until 00:00 the following day, and you can also create an event for just the calendar date, and these are separate things.
A 24-hour interval and a calendar date. Although notably, Google Calendar collapses the 24-hour event into a calendar-date event if you do this.
“Doesn’t matter, just pick one convention and stick with it.” I hope after reading this post you’re convinced that it does matter, depending on your use case.
“Ugh!” That’s how I feel too and why I wrote a whole blog post about it!
How do I feel about the choices we made in Temporal?
I’m happy with how Temporal encourages the programmer to handle these cases. When I went to try out the comparisons that were suggested in the original tweet, I found it was natural to pick either PlainDate or PlainDateTime to represent the data.
One thing that Temporal could have done instead (and in fact, we went back and forth on this a few times before the proposal reached its currently frozen stage in the JS standardization process) would be to make the choice of data type, and therefore of comparison semantics, more explicit.
For example, one might make a case that it’s potentially confusing that the 12:00:00 part of the string in Temporal.PlainDate.from('2022-06-01').equals('2022-06-01 12:00:00') is ignored when the string is converted to a PlainDate. We could have chosen, for example, to throw if the argument to PlainDate.prototype.equals() was a string with a time in it, or if it was a PlainDateTime. That would make the code for answering question 1 look like this:
> Temporal.PlainDate.from('2022-06-01').equals(
... Temporal.PlainDateTime.from('2022-06-01 12:00:00')
... .toPlainDate())
true
This approach seems like it’s better at forcing the programmer to make a choice consciously by throwing exceptions when there is any doubt, but at the cost of writing such long-winded code that I find it difficult to follow. In the end, I prefer the more balanced approach we took.
Conclusion
This was a really interesting problem to dig into. I always find it good to be reminded that no matter what I think is correct about date-time handling, someone else is going to have a different opinion, and they won’t necessarily be wrong.
I said in the beginning of the post: “to get a good answer, you need to specify which concept you are talking about.” Something we’ve tried hard to achieve in Temporal is to make it easy and natural, but not too obtrusive, to specify this. When I went to answer the questions using Temporal code, I found it pretty straightforward, and I think that validates some of the design choices we made in Temporal.
I’d like to acknowledge my employer Igalia for letting me spend work time writing this post, as well as Bloomberg for sponsoring Igalia’s work on Temporal. Many thanks to my colleagues Tim Chevalier, Jesse Alama, and Sarah Groff Hennigh-Palermo for giving feedback on a draft of this post.
[1] 777 days at the time of writing, according to Temporal.PlainDate.from('2020-01-13').until(Temporal.Now.plainDateISO())
[2] A common source of bugs with JavaScript’s legacy Date when the made-up time of day doesn’t exist due to DST
[3] “Semantics” is, unfortunately, a word I’m going to use a lot in this post
[4] “Time” in Temporal refers to a time on a clock face, with no date associated with it
[5] We even say this on the PlainDateTime documentation page
[6] We don’t have methods like isBefore()/isAfter() in Temporal, but this is a place where they’d be useful. These methods seem like good contenders for a follow-up proposal in the future
[7] Intervals bring all sorts of tricky questions too! Some other date-time libraries have interval objects. We also don’t have these in Temporal, but are likewise open to a follow-up proposal in the future [Less]
|