Skip to content

Conversation

@Renaud-Barrau
Copy link

Hello,
First of all, thanks for this nice project !

I open this PR because I encounter a limitation in my use case.
In fact, I work on an embedded device that have 2 connectors (LVDS-1 and HDMI-A-1), but on the same drm device /dev/dri/card0.
My goal is to have flutter-pi running on the LVDS screen, while the HDMI screen is displaying something else. As far as I can understand, this is not possible because flutter-pi is locking the drm master.

That's why I added the option "--drm-fd" to the flutter-pi command-line that takes a valid and opened drm file descriptor.
I used the drm-lease-manager (https://github.com/AGLExport/drm-lease-manager) to create a lease on the wanted connector, and passed the fd retrieve with the client API.

This works like a charm, and the behaviour is the same as before if the --drm-fd option is not passed :)

I did my best to understand and modify the original code as little as possible.

Any suggestions/improvements/tips are welcome 👍

Renaud Barrau added 2 commits October 28, 2025 14:25
This allows to start flutter-pi on a specific DRM fd, instead of letting the tool locking the DRM master.
This can be useful if we have several screens and we want to display the flutter-pi content on a specific one, while not locking others.

This works well with the drm-lease-manager project (https://github.com/AGLExport/drm-lease-manager/tree/master)
@ardera
Copy link
Owner

ardera commented Oct 31, 2025

Hey, thanks for the PR!

I've seen DRM lease manager before; do you have a quick summary on how I would use this together with lease manager? did you build a small shell around flutter-pi that calls dlm_get_lease and dlm_lease_fd and then jumps to the flutter-pi main?

@Renaud-Barrau
Copy link
Author

Yes indeed !
First, you need to start the drm-lease-manager server with a correct config file.
As an example, my config file looks like :

[[lease]]
name="LVDS-screen"
connectors=["LVDS-1"]
[[lease]]
name="HDMI-screen"
connectors=["HDMI-A-1"]

(Personally, I created a systemd service for it to start at boot)

Then, the idea is to write a wrapper around whatever app you want to launch, as long as it accept a drm fd as input.
In the wrapper, you need to use the libdrmclient to retrive the fd from the server.

Again, everything is better with example 😄
Following, a simple C wrapper I wrote that takes as an argument the lease-name (in my case "LVDS-screen" or "HDMI-screen") and some options to pass the command to launch.
-c : launch the command that follows this option
-cmd : launch the command that is written inside a file specified by this option.
You will notice that I use an environment variable to store the fd : $DRM_FD, but this is not mandatory

Example on how to use the wrapper :
./lease_launcher LVDS-screen -cmd flutterpi-cmd.conf

with flutterpi-cmd.conf which contains :
LD_LIBRARY_PATH=/run/media/sda1/bundle_release/lib:$LD_LIBRARY_PATH /usr/bin/flutter-pi --release -d 271,203 --drm-fd $DRM_FD /run/media/sda1/bundle_release/

Other example with gstreamer (that accepts drm fd through kmssink) :
./lease_launcher HDMI-screen -cmd gstreamer-cmd.conf

with gstreamer-cmd.conf which contains :
while true; do GST_KMS_DRM_DEVICE=/dev/dri/card0 gst-launch-1.0 filesrc location=/root/fire.mp4 ! qtdemux ! h264parse ! v4l2h264dec ! kmssink fd=$DRM_FD; sleep 1; done

And here is the lease_launcher.c wrapper :

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <errno.h>
#include "libdlmclient/dlmclient.h"

#define MAX_CMD_SIZE 2048
#define MAX_LINE_SIZE 1024

typedef struct {
    char cmd[MAX_CMD_SIZE];
} Config;

int load_config_from_file(const char *config_file, Config *config) {
    FILE *fp = fopen(config_file, "r");
    if (!fp) {
        perror("Impossible d'ouvrir le fichier config");
        return 0;
    }

    char line[MAX_LINE_SIZE];
    while (fgets(line, sizeof(line), fp)) {

        if (line[0] == '#' || line[0] == '\n') continue;

        line[strcspn(line, "\n")] = '\0';
        strncpy(config->cmd, line, sizeof(config->cmd) - 1);
        fclose(fp);
        return 1;
    }

    fprintf(stderr, "Fichier config vide ou invalide\n");
    fclose(fp);
    return 0;
}

void print_usage(const char *prog_name) {
    fprintf(stderr, "Usage: %s <lease-name> [options]\n", prog_name);
    fprintf(stderr, "Options:\n");
    fprintf(stderr, "  -c, --config <file>      Charger la commande depuis un fichier. La variable d'environnement $DRM_FD contient le fd récupéré via l'API client de drm-lease-manager\n");
    fprintf(stderr, "  -cmd <command>           Spécifier la commande directement\n");
    fprintf(stderr, "  -h, --help               Afficher cette aide\n");
}

int main(int argc, char **argv) {
    if (argc < 2) {
        print_usage(argv[0]);
        return EXIT_FAILURE;
    }

    const char *socket_path = argv[1];
    Config config = {0};

    for (int i = 2; i < argc; i++) {
        if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) {
            print_usage(argv[0]);
            return EXIT_SUCCESS;
        } else if ((strcmp(argv[i], "-c") == 0 || strcmp(argv[i], "--config") == 0) && i + 1 < argc) {
            if (!load_config_from_file(argv[++i], &config)) {
                return EXIT_FAILURE;
            }
        } else if (strcmp(argv[i], "-cmd") == 0 && i + 1 < argc) {
            strncpy(config.cmd, argv[++i], sizeof(config.cmd) - 1);
        } else {
            fprintf(stderr, "Option inconnue: %s\n", argv[i]);
            print_usage(argv[0]);
            return EXIT_FAILURE;
        }
    }

    if (config.cmd[0] == '\0') {
        fprintf(stderr, "Erreur: aucune commande spécifiée\n");
        print_usage(argv[0]);
        return EXIT_FAILURE;
    }

    struct dlm_lease *lease = dlm_get_lease(socket_path);
    int drm_device_fd = dlm_lease_fd(lease);

    printf("✅ FD DRM lease reçu : %d\n", drm_device_fd);

    char env_var[64];
    snprintf(env_var, sizeof(env_var), "%d", drm_device_fd);
    setenv("DRM_FD", env_var, 1);

    printf("▶️ Exécution : %s\n", config.cmd);
    fflush(stdout);

    execl("/bin/sh", "sh", "-c", config.cmd, NULL);

    perror("execl");
    dlm_release_lease(lease);
    return EXIT_FAILURE;
}

(Comments are in french, sorry 😉 )

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants