A => .envrc +1 -0
A => .gitignore +2 -0
@@ 1,2 @@
+/.direnv/
+/test/
A => README.md +11 -0
@@ 1,11 @@
+# install-garmin-watch-face
+
+A small program to install a Garmin watch face.
+
+Like, really small, because it's actually just moving a file to a mounted
+directory. Yeah...
+
+Depends on [simple-mtpfs](https://github.com/phatina/simple-mtpfs).
+
+Maybe make hare bindings for https://github.com/libmtp/libmtp and do it
+directly, instead of depending on runtime external binaries.
A => flake.lock +27 -0
@@ 1,27 @@
+{
+ "nodes": {
+ "nixpkgs": {
+ "locked": {
+ "lastModified": 1752517496,
+ "narHash": "sha256-nHo/Gle6z6lMZc5kGtMOBKkVw1/Z9ilYdV2e14+Q6bU=",
+ "owner": "liljamo",
+ "repo": "nixpkgs",
+ "rev": "98bc1dc9a61dc6350ab81625410bab51f28d36ac",
+ "type": "github"
+ },
+ "original": {
+ "owner": "liljamo",
+ "ref": "hare-0.25.2",
+ "repo": "nixpkgs",
+ "type": "github"
+ }
+ },
+ "root": {
+ "inputs": {
+ "nixpkgs": "nixpkgs"
+ }
+ }
+ },
+ "root": "root",
+ "version": 7
+}
A => flake.nix +19 -0
@@ 1,19 @@
+{
+ inputs = {
+ #nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
+ nixpkgs.url = "github:liljamo/nixpkgs/hare-0.25.2";
+ };
+
+ outputs = inputs @ {...}: let
+ pkgs = inputs.nixpkgs.legacyPackages."x86_64-linux";
+ in {
+ devShells.x86_64-linux.default = pkgs.mkShell {
+ buildInputs = with pkgs;
+ [
+ hare
+
+ simple-mtpfs
+ ];
+ };
+ };
+}
A => main.ha +86 -0
@@ 1,86 @@
+use fs;
+use getopt;
+use log;
+use os;
+use os::exec;
+use path;
+use strings;
+use temp;
+
+const GARMIN_APPS = "/GARMIN/APPS/";
+
+export fn main() void = {
+ const cmd = getopt::parse(os::args,
+ "garmin watch face installer",
+ ('s', "skip MTP mount (testing)"),
+ "file",
+ );
+ defer getopt::finish(&cmd);
+
+ let skip_mount = false;
+ for (let opt .. cmd.opts) {
+ switch (opt.0) {
+ case 's' =>
+ skip_mount = true;
+ case => abort();
+ };
+ };
+
+ let file = "";
+ if (len(cmd.args) < 1) {
+ log::fatal("provide a file");
+ } else {
+ file = os::resolve(cmd.args[0]);
+ if (!os::exists(file)) {
+ log::fatal("provided file doesn't exist");
+ };
+ let parts = strings::split(file, ".")!;
+ defer free(parts);
+ if (parts[len(parts) - 1] != "prg") {
+ log::fatal("file does not seem to be a .prg file");
+ };
+ };
+
+ const temp_dir = temp::dir();
+ defer os::rmdirall(temp_dir)!;
+ log::printfln("created temporary directory: {}", temp_dir);
+
+ if (skip_mount) {
+ os::mkdirs(strings::concat(temp_dir, GARMIN_APPS)!, fs::mode::USER_RWX)!;
+ } else {
+ mount_mtp(temp_dir);
+ };
+
+ const apps_dir = strings::concat(temp_dir, GARMIN_APPS)!;
+ if (!os::exists(apps_dir)) {
+ log::fatal("/GARMIN/APPS doesn't exist in mount");
+ };
+
+ const file_dest = strings::concat(apps_dir, "face.prg")!;
+ log::printfln("moving {} to {}", file, file_dest);
+ os::move(file, file_dest)!;
+
+ // Remember to unmount the MTP fs.
+ if (!skip_mount) {
+ umount_mtp(temp_dir);
+ };
+
+ log::println("your new watch face should now show up");
+};
+
+fn mount_mtp(temp_dir: str) void = {
+ log::println("mounting MTP");
+ let cmd = exec::cmd("simple-mtpfs", temp_dir)!;
+ let proc = exec::start(&cmd)!;
+ let status = exec::wait(&proc)!;
+ if (status.status != 0) {
+ log::fatal("simple-mtpfs failed, most likely a permission issue");
+ };
+};
+
+fn umount_mtp(temp_dir: str) void = {
+ log::println("umounting MTP");
+ let cmd = exec::cmd("fusermount", "-u", temp_dir)!;
+ let proc = exec::start(&cmd)!;
+ let status = exec::wait(&proc)!;
+};