Category Archives: Knowledge Base

How to fix Gradle -P arguments not working in PowerShell

Executing this command in PowerShell:

./gradlew createGplayDebugCoverageReport -Pcoverage=true -Pandroid.testInstrumentationRunnerArguments.class=com.owncloud.android.datamodel.FileDataStorageManagerContentProviderClientIT

gave me the weird error

A problem occurred evaluating project ':app'.
> No such property: applicationVariants for class: java.lang.String

Turns out, PowerShell can interpret the -P as a parameter for the script or command, not as an argument to Gradle, which leads to parsing problems.

Solution: Use –% to Stop PowerShell from Parsing Arguments

./gradlew --% createGplayDebugCoverageReport -Pcoverage=true -Pandroid.testInstrumentationRunnerArguments.class=com.owncloud.android.datamodel.FileDataStorageManagerContentProviderClientIT

Bonus: I used Github Copilot to have this explained to me and it actually hallucinated a reference.

It gave a link to https://stackoverflow.com/questions/36540081/gradle-p-arguments-not-working-in-powershell which actually resolves to an unrelated article https://stackoverflow.com/questions/36540015/how-to-reduce-apk-apk-size-in-android-studio/36540081#36540081. So I figured, let’s make this article a reality!

How to keep your fitness tracker health data to yourself

For a long time I have refrained from using fitness trackers or sport watches because I never found the time to dive into questions like:

  1. Which fitness tracker can be used without a mandatory mobile app?
  2. How can I access my fitness data conveniently but keep it away from “the cloud”?
    • Or: How can I backup fitness data on “my cloud”?
  3. Which fitness tracker works well with the Open Source ecosystem?

Well, I finally did and now this is my setup:

  1. Garmin Instinct 2
  2. The open source Android app Gadgetbridge

Honestly, the watch works well enough on its own. You need to get used to its 90s-Casio-watch-style control using five hardware buttons, but once you learned it, you could access all the relevant data without any additional connection.

But still, for a bit more convenience and more in-depth details of fitness and activity data, Gadgetbridge is quite nice. Inside the app, you can easily connect to the Garmin watch via Bluetooth.

Fetching and updating activity data is by default done manually via button press, since it may take 10-15 seconds. Once the data is downloaded from the watch, you can dive into all the details about your activities, sleep, heart rate, etc…

Screenshots from https://gadgetbridge.org/basics/features/activities/

There is a couple of maintenance steps you’ll need perform manually from time to time, as Gadgetbridge cannot interfere too much with the IP of the original manufacturers.

Firmware updates

Gadgetbridge allows you to upload updated firmware to the device, but it doesn’t tell you where to get these files from, most likely out of fear of retaliation.

So how do you get the firmware files for your Garmin watch? From a non-shady source, preferably? Easy: You can find them on Garmin’s official forum. More specifically: Their beta builds published for Side Loading contain the last official build as well, for an easy roll-back.

As an example, see their announcement Beta Version 17.04 – Side Load (Archive.org link) containing zip archives for various watch models.

  1. Download the zip file for your model
  2. Inside, you find the latest official build under SystemBackdate_XX.XX/GUPDATE.GCD
  3. Open Gadgetbridge, connect to your watch
  4. Click the three dot menu next to your watch, then “File Installer”
  5. Select the .gcd file and upload it to the watch

AGPS updates

AGPS is responsible for speeding up your GPS-based localization and make it more precise. For that, it relies on (pre-)computed satellite orbit and correction data. This must be refreshed from time to time, e.g. every 30 days.

  1. Open Gadgetbridge, connect to your watch
  2. Click on the gear to open the device-specific settings
  3. Click on “Location” and scroll down where it says “Folder”. Set a folder where you will download the AGPS file in a minute.
  4. After folder selection, back on the “Location” screen, see the AGPS 1 URL. Something like https://api.gcs.garmin.com/...
  5. Click on it to copy it to the clipboard. Open the link in a webbrowser to download the file to the folder you set before.
  6. Back on the “Location” screen, directly under the URL, select the “Local file” you’ve just downloaded
  7. The “Status” should switch to “Pending”. Whenever the watch requests an AGPS update, Gadgetbridge will now intercept that call and deliver the file. You’ll see that some days later, the Status will then show “Current”

(Cloud) Backup

The automatic export periodically stores the Gadgetbridge database at a location of your choice, which can also be an online folder, e.g. from Nextcloud if you have the app installed. The important caveat is: This only stores the already processed data from Gadgetbridge, not the raw files from the device (e.g. .fit files in case of Garmin):

  1. In the app’s settings (not the device settings!) go to “Automations”
  2. Toggle the switch for “Auto export enabled” to ON
  3. Under “Export location” select the folder where to export the Gadgetbridge.db to.

If you want to also get the raw files from the device backed up, this needs to be triggered manually:

  1. In the app, open the tab “Data management”
  2. Click “Export zip” and store the file at a location of your choice
  3. The resulting file contains the Gadgetbridge database under database/Gadgetbridge and the raw device files under files/<device ID>/

More about backups, including examples of how to process the data in the Gadgetbridge manual.

How to create a new Unity project without a cloud connection

With their recent Unity Hub 3.13.0 upgrade, they have removed the option for not connecting a project to the Unity Cloud. Because all that yummy data, I guess…

This is how to setup a project without uploading everything to Unity’s servers:

  1. If you want to use a project template you’ve not used before, create a dummy project with that template (which will be uploaded to the cloud). Give it a funny name, maybe
  2. The template will be downloaded to your local app data
    • Windows: %APPDATA%\UnityHub\Templates
  3. In that folder you find .tgz archives for each template. Select the one you need and pull the contents of package/ProjectData~ out.
  4. Place it into a new folder with your desired project name.
  5. In Unity Hub, click “Add” next to the “+New project” button and add a project from disk

I found it funny to illustrate a post about a Microsoft bug with a Microsoft Copilot generated image containing garbled text

Can’t open a Microsoft Outlook protected message? This is how you work around it

Email encryption forever is a pain point in the IT ecosystem. PGP is a great system but hasn’t been widely adopted.
This is how I personally have used PGP in the past, for the <10 PGP emails I have received over my entire lifetime:

https://xkcd.com/1181
(Yes, I know the joke is more about sender verification and less about encryption itself)

Microsoft has rolled out their own solution to the problem: Outlook Protected Messages. A proprietary system on top of an open, wide-spread standard – I don’t really like that but hey, it is better than nothing!
If an Outlook user sends you such a protected email to a non-Outlook and non-GMail address, you will receive an email “Alice has sent you a message that was protected with Microsoft Office 365” and a link to click. You’ll be redirected to a page where you can sign in and receive a single-use code sent to your email address.

But here is the catch: This sign in just doesn’t work! The email I received these messages is not connected to a Microsoft account. So I could not login to request the single-use code. I then tried it with an email which was connected to a Microsoft account – turns out, this also doesn’t work! Even if the protected email was sent to this Microsoft account.

Somehow, the solution to this is to trick Microsoft into your browser being a mobile browser. Then, you are not asked for any sign in but can directly request the single-use code. This is how you do it, using the Browser’s developer tools:

  1. Copy the link from the protected message
  2. Open a new empty browser tab.
  3. Right-click → Inspect
  4. Click the “device icon”
    • On Firefox, it is on the right side of the bar
    • On Chrome, it is on the left side of the bar
  5. Your browser now acts as a mobile browser.
  6. Enter the link into the address bar
  7. Request the single-use code to be sent to your email
  8. Then enter the received code in the browser

Privacy in the Metaverse

Or: How to install any app on the Quest 3 without giving Meta your phone number.

With the long anticipated Apple Vision Pro become available at February 2nd 2024 (unfortunately only in the US), we’ll finally see Apple’s take on a consumer-ready headset for mixed reality – er … I meant to say spatial computing. Seamless video see-through and hand tracking – what a technological marvel.

As of now, the closest alternative to the Vision Pro, those unwilling to spend $3500 or located outside the US, seems to be the Meta Quest 3. And this only at a fraction of the price, at $500. But unlike Apple, Meta is less known for privacy-aware products. After all, it is their core business model to not be.

This post explains how to increase your privacy on the Quest 3, in four easy steps.

Continue reading Privacy in the Metaverse

C++ Weihnachtsspecial

English summary: This is a holiday special for my German C++ beginners book, hence this post is in German.

Dieser Blogpost erweitert Ihr Wissen aus dem Buch C++ Schnelleinstieg: Programmieren lernen in 14 Tagen. Nach dem Kapitel 9 (Fortgeschrittene Konzepte) haben Sie alle nötigen Grundlagen, die Sie als Voraussetzung für das Special benötigen:

  • Grundlagen der Objektorientierung
  • Bibliotheken mittels vcpkg einbinden
  • GUI-Programmierung mit Nana
  • Callbacks und Lambdas
  • Fehler werfen

In diesem Projekt soll ein Weihnachtsbaum programmiert werden, bei dem Sie die Kerzen per Mausklick anzünden können. So sieht das ganze aus, wenn es fertig ist:

Das Codebeispiel beginnt in der main-Funktion des Programms und fügt nach und nach zusätzliche Komponenten oberhalb ein – daher beginnt die Zeilennummerierung so wie sie in der fertigen Datei sein wird (Downloadlink am Ende des Beitrags). Der Code wird durch Erklärungen unterbrochen, wie Sie an den fortlaufenden Zeilennummern erkennen können.

int main()
{
  nana::paint::image tree("tree.png");
  if (tree.empty())
  {
    /* Sie müssen die drei Bilder (tree.png, candleOn.png, candleOff.png) in den
       Build-Ordner kopieren. Sie finden diesen, indem Sie in Visual Studio im
       Projektmappen-Explorer einen Rechtsklick auf das Projekt durchführen und
       "Ordner in Datei-Explorer öffnen" auswählen.
    */
    throwException("Konnte das Baumbild nicht laden!");
  }

Die Funktion throwException wird später noch implementiert. Die für das Programm benötigten drei Bilder des Baums, der erloschenen und der angezündeten Kerze finden Sie zusammen mit der fertigen Codedatei am Ende dieses Beitrags. Nun geht es um die grundlegende Struktur: Das Fenster sowie die Kerzen.

  // Ein Nana-Formular als Basis des Fensters anlegen
  nana::form window(nana::API::make_center(tree.size().width, tree.size().height));
  window.caption("C++ Weihnachtsspecial 2021");

  // Jede Codezeile beim Anlegen der Liste entspricht einem Stockwerk des Baumes
  std::vector<Candle> candles = { Candle(290, 200),
    Candle(200, 300), Candle(300, 330), Candle(400, 315),
    Candle(120, 430), Candle(250, 450), Candle(350, 450), Candle(480, 450),
    Candle(70, 580), Candle(170, 620), Candle(290, 630), Candle(420, 625), Candle(540, 600) };

Die Kerzen werden als Objekte modelliert und mit x- und y-Koordinaten angelegt. Die Klassendefinition folgt gleich, nachdem der Rest der main-Funktion besprochen wurde. Folgen Sie solange dem im Abschnitt 4.3 des Buches erläuterten Prinzip des “Wishful Programmings” und nehmen Sie einfach an, Sie hätten schon eine solche Klasse, die Ihnen alle Wünsche erfüllt. Nun sollen die Grafiken alle gezeichnet werden:

  // Zeichnen des Baumes und der Kerzen
  nana::drawing drawing(window);
  drawing.draw([&](nana::paint::graphics& graphics)
  {
    tree.paste(graphics, nana::point(0, 0));
    for (Candle& candle : candles)
    {
      candle.draw(graphics);
    }
    graphics.string(nana::point(10, static_cast<int>(window.size().height) - 20),
      "C++ Schnelleinstieg: Programmieren lernen in 14 Tagen. Philipp Hasper, 2021");
  });

Das Zeichnen der weihnachtlichen Szenerie übernimmt die Nana-Klasse nana::drawing innerhalb einer Lambdafunktion. Sie sehen hier zwei praktische Methoden in Aktion:

  • tree.paste() kopiert das Bild des Baumes an eine bestimmte Stelle – hier an die Stelle (0,0) was der linken oberen Ecke entspricht. Hierfür muss auch noch das graphics-Object übergeben werden, welches aus dem Parameter der Lambdafunktion kommt.
  • graphics.string() schreibt einen Text an eine bestimmte Stelle. Hier soll es in die linke untere Ecke, daher muss für die y-Koordinate von der Höhe des Fensters genug Platz abgezogen werden, sodass der Text noch hinpasst.

Zum Schluss fehlen noch die Klickereignisse. Da die Kerzen als Grafiken gezeichnet wurden, haben sie kein automatisches Klick-Event wie zum Beispiel eine Schaltfläche. Daher muss jeder Klick ins Fenster abgefangen und darauf überprüft werden, ob seine Koordinaten innerhalb einer Kerze liegen:

  // Jeden Klick überprüfen, ob er eine Kerze trifft
  window.events().click([&](const nana::arg_click& event) {
    if (event.mouse_args == nullptr)
    {
      // Event kann nicht verarbeitet werden
      return;
    }
    for (Candle& candle : candles)
    {
      if (candle.isClicked(event.mouse_args->pos))
      {
        candle.toggle();
        /* Hier könnte man mit einem return die Schleife verlassen.
           Da sich aber bei falscher Platzierung die Kerzen überlagern
           könnten, wird die Schleife noch zu Ende geführt.
        */
      }
    }
    // Zeichnung wiederholen, sodass das Bild aktualisiert wird.
    drawing.update();
  });

  // Fenster anzeigen und Nana starten
  window.show();
  nana::exec();
  return 0;
}

Ob eine Kerze geklickt wurde, wird innerhalb der Candle-Klasse überprüft. Auch die Reaktion auf einen Klick ist in dieser Klasse implementiert. Fügen Sie daher diese Klasse oberhalb der main-Funktion ein:

// Klasse zum Zeichnen einer Kerze in zwei Zuständen (angezündet, ausgeblasen)
class Candle {
private:
  nana::paint::image candleOn;
  nana::paint::image candleOff;
  bool isOn = false;
  nana::rectangle rectangle;
public:
  Candle(int centerX, int centerY)
    : candleOn("candleOn.png"),
      candleOff("candleOff.png")
  {
    if (candleOn.empty() || candleOff.empty())
    {
      throwException("Konnte die Kerzenbilder nicht laden!");
    }
    if (candleOn.size() != candleOff.size())
    {
      throwException("Kerzenbilder haben nicht die gleiche Größe!");
    }
    /* Die an den Konstruktor übergebene x - und y - Koordinate sollen das Zentrum
       der Kerze angeben, daher müssen Sie für das Ziel-Rechteck umgerechnet werden.
     */
    nana::size size = candleOn.size();
    rectangle = nana::rectangle(centerX - size.width/2,
                                centerY - size.height/2,
                                size.width,
                                size.height);
  }

  void draw(nana::paint::graphics& graphics)
  {
    if (isOn)
    {
      candleOn.paste(graphics, rectangle.position());
    }
    else
    {
      candleOff.paste(graphics, rectangle.position());
    }
  }

  bool isClicked(const nana::point& point)
  {
    // Diese Methode testet, ob der Punkt innerhalb des Rechtecks liegt
    return rectangle.is_hit(point);
  }

  void toggle()
  {
    isOn = !isOn;
  }
};

Diese Klasse handhabt zwei verschiedene Bilder, die an einem Punkt zentriert angezeigt werden sollen. Abhängig vom Zustand (isOn), wird das eine oder das andere Bild gezeichnet. Zur einfacheren Platzierung wird beim Erzeugen des Objektes das gewünschte Zentrum der Kerze angegeben, was dann innerhalb des Konstruktors in ein entsprechendes Rechteck umgerechnet werden muss.

Nun fehlt noch eine Hilfsfunktion, die eine Fehlermeldung auf der Konsole ausgibt und dann einen Fehler wirft. Fügen Sie diese am Anfang der Datei, nach den include-Befehlen ein:

// Funktion, einen Fehler auf der Konsole ausgibt und dann einen Fehler wirft
void throwException(const std::string& msg)
{
  std::cout << msg << std::endl;
  throw std::exception(msg.c_str());
}

Und das war auch schon das ganze Programm. Frohe Feiertage und einen guten Rutsch! Den vollständigen Code und die Bilder können Sie hier herunterladen:

Jekyll plugin to bundle zip archives

The website for my recently published C++ book, cpp.hasper.info (German), was made with the static site generator Jekyll. It contains additional information, such as the code of all projects inside the book, as well as the sample solution for the exercises at the end of each chapter.

The reason why I used a static site generator in the first place was that I had all the code files organized in a folder, equipped with a CMake file which made sure the projects compile and are statically analyzed (I used cppcheck and cpplint, albeit with a very reduced set of checks due to the nature of the code examples). In order to not destroy this automation by copy-pasting code into a CMS, the site had to be generated around the code files.

I also wanted to enable the download of all C++ files as zip archive on a per-chapter basis. Again – I did not want to manually create this archive, in case I had to change some code in the future. So I wrote a Jekyll plugin which bundles given files into a zip archive which then can be placed behind a download link.

How it is used:

Filenames as multiple parameters:

{% zip archiveToCreate.zip file1.txt file2.txt %}

Spaces in filenames:

{% zip archiveToCreate.zip file1.txt folder/file2.txt 'file with spaces.txt' %}

A variable to contain a list of files is also possible:

{% zip ziparchiveToCreate.zip {{ chapter_code_files }} %}

The plugin code:

The plugin can be found here: https://github.com/PhilLab/jekyll-zip-bundler

# frozen_string_literal: true

# Copyright 2021 by Philipp Hasper
# MIT License
# https://github.com/PhilLab/jekyll-zip-bundler

require 'jekyll'
require 'zip'
# ~ gem 'rubyzip', '~>2.3.0'

module Jekyll
  # Valid syntax:
  # {% zip archiveToCreate.zip file1.txt file2.txt %}
  # {% zip archiveToCreate.zip file1.txt folder/file2.txt 'file with spaces.txt' %}
  # {% zip {{ variableName }} file1.txt 'folder/file with spaces.txt' {{ otherVariableName }} %}
  # {% zip {{ variableName }} {{ VariableContainingAList }} %}
  class ZipBundlerTag < Liquid::Tag
    VARIABLE_SYNTAX = /[^{]*(\{\{\s*[\w\-.]+\s*(\|.*)?\}\}[^\s{}]*)/mx.freeze
    CACHE_FOLDER = '.jekyll-cache/zip_bundler/'

    def initialize(tag_name, markup, tokens)
      super
      # Split by spaces but only if the text following contains an even number of '
      # Based on https://stackoverflow.com/a/11566264
      # Extended to also not split between the curly brackets of Liquid
      # In addition, make sure the strings are stripped and not empty
      @files = markup.strip.split(/\s(?=(?:[^'}]|'[^']*'|{{[^}]*}})*$)/)
                     .map(&:strip)
                     .reject(&:empty?)
    end

    def render(context)
      # First file is the target zip archive path
      target, files = resolve_parameters(context)
      abort 'zip tag must be called with at least two files' if files.empty?

      zipfile_path = CACHE_FOLDER + target
      FileUtils.makedirs(File.dirname(zipfile_path))

      # Create the archive. Delete file, if it already exists
      File.delete(zipfile_path) if File.exist?(zipfile_path)
      Zip::File.open(zipfile_path, Zip::File::CREATE) do |zipfile|
        files.each do |file|
          # Two arguments:
          # - The name of the file as it will appear in the archive
          # - The original file, including the path to find it
          zipfile.add(File.basename(file), file)
        end
      end
      puts "Created archive #{zipfile_path}"

      # Add the archive to the site's static files
      site = context.registers[:site]
      site.static_files << Jekyll::StaticFile.new(site, "#{site.source}/#{CACHE_FOLDER}",
                                                  File.dirname(target),
                                                  File.basename(zipfile_path))
      # No rendered output
      ''
    end

    def resolve_parameters(context)
      # Resolve the given parameters to a file list
      target, files = @files.map do |file|
        next file unless file.match(VARIABLE_SYNTAX)

        # This is a variable. Look it up.
        context[file]
      end

      [target, files]
    end
  end
end

Liquid::Template.register_tag('zip', Jekyll::ZipBundlerTag)

How to remove Unity Services from your project

I recently found myself in the situation that a Unity project which changed ownership, still linked to now non-existing Unity Services. It constantly gave me warning messages like:

Unable to access Unity services. Please log in or request membership to this project to use these services

These were only warnings, so nothing particularly dangerous, but still I wanted to get rid of them. As it took me multiple iterations to solve it and there are multiple unsolved or incomplete post about it, I wanted to share here how I finally managed to solve this problem:

  1. Navigate to the file <Your project folder>/ProjectSettings/ProjectSettings.asset and open it with a text editor
  2. In the section cloudServicesEnabled, set all to 0
  3. Set the cloudProjectId empty (i.e. a space after the colon)

Especially the space part took me a while to figure it out. The respective sections of the Unity project’s settings file now look similar to this:

...  
  vrEditorSettings:
    daydream:
      daydreamIconForeground: {fileID: 0}
      daydreamIconBackground: {fileID: 0}
  cloudServicesEnabled:
    UNet: 0
  luminIcon:
    m_Name: 
    m_ModelFolderPath: 
    m_PortalFolderPath: 
  luminCert:
    m_CertPath: 
    m_SignPackage: 1
  luminIsChannelApp: 0
  luminVersion:
    m_VersionCode: 1
    m_VersionName: 
  apiCompatibilityLevel: 6
  cloudProjectId: 
  framebufferDepthMemorylessMode: 0
...

Having these modifications in the project settings made the warning disappear, as the unused cloud services are now properly removed from the project. However, given that Unity sometimes changes their framework and configurations, this might get out-dated some day in the near future. If this doesn’t work anymore when you are trying it, please drop me a message, so I can retry and update the article. But so far, the warning has not popped up again in any of my Unity projects.

Custom backgrounds in Microsoft Teams video calls

Update: In newer versions of Teams, a button has been added to add custom images: Just go to the Background settings and click “+ Add New”.

Microsoft Teams allows you to blur or replace the background with pre-defined images. However, there is no user interface which allows to upload custom images.

It turns out that it is just the upload button which is missing – if you know where the folder lies from which Teams fetches the backgrounds, you can easily place your own content in there.

  • On Windows, this is: %AppData%\Microsoft\Teams\Backgrounds\Uploads, i.e. C:\Users\<your user name>\AppData\Roaming\Microsoft\Teams\Backgrounds\Uploads
  • On Mac, this is: /users/<your user name>/Library/Application Support/Microsoft/Teams/Backgrounds/Uploads

This allows for some creative effects:

To subtle? What about this:

P.S.: One nifty trick to bewilder your colleagues: Take a screenshot during the next video call, use Gimp to retouch the person from the image and use their room as your background

(extra blur added for this screenshot)

How we organize a small development team with minimal overhead in Gitlab

At ioxp we develop a novel way of sharing knowledge using Augmented Reality, AI and Computer Vision to change the way how industries operate. Our ecosystem consists of many different tools and technologies – the AI backend, Hololens apps , Android apps, a web-based editor and cloud services – so we are very dependent on an efficient way to organize our developers and their different, often overlapping sub-teams.

And after all we are a startup and engineers by heart – we have to keep the balance between a thorough designed process and the creative freedom needed to explore and create new things.

This post will concentrate on how we facilitate developer communication and team work using features of the DevOps tool Gitlab, factoring out all other important stages like product management, planning or sprint planning. It is an excerpt from our internal Handbook (naturally hosted as Wiki in Gitlab) and describes the process I have designed and implemented together with my team.

The development process is primarily guided by issues and their three most important attributes: labels, assignees and related merge requests. It uses as little as 10 different issue labels and is divided into six stages:

Continue reading How we organize a small development team with minimal overhead in Gitlab