finalcut icon indicating copy to clipboard operation
finalcut copied to clipboard

Qt compatibility

Open ocgltd opened this issue 11 months ago • 10 comments

Feature Request

Do you want to solve a problem with your feature request? Please describe it.

I build Qt apps (console and GUI) and now have to build a terminal app with pretty IO, block graphics, etc. Instead of using ncurses I would really like to try this project. Curiously, the structure of this project seems to be very much in the style of Qt (which is great).

But, right from the start, finalcut needs to control the main application event loop, which would make it conflict with Qt doing the same. Has anyone succesfully combined finalcut and Qt5 ?

Your desired solution

An example of how to combine them (if possible)

Alternatives you have considered

Additional context

ocgltd avatar Feb 10 '25 13:02 ocgltd

You must first determine whether an active X11/Wayland session exists. If this is the case, you must switch to the appropriate branch.

Here is a very simplified code that shows how this could be implemented in principle:

#include ...

int main (int argc, char argv[])
{
  const std::string display_env = std::getenv("DISPLAY");
  const std::string wayland_env = std::getenv("WAYLAND_DISPLAY");
  
  if ( ! display_env.empty() || ! wayland_env.empty() )
  {
    // Launch Qt based GUI application

    QApplication app (argc, argv);
    QLabel label("Hello, World!");
    label.resize(200, 100);
    label.show();
    return app.exec();
  }
  else
  {
    // Launch FINAL CUT based TUI application

    finalcut::FApplication app{argc, argv};
    finalcut::FMessageBox msg_box{&app};
    msg_box.setText("Hello, World!");
    return msg_box.exec();
  }
}

gansm avatar Feb 10 '25 15:02 gansm

Not sure I understand the answer., My program is a console only app, and doesn't care about any X11/Wayland sessions that may also be present.

By my console app does use threads and timers so I need to start the event queue in the main thread....but finalcut wants to do the same. If I understand your example correctly you are saying I don't need to start the Qt event loop?

ocgltd avatar Feb 10 '25 17:02 ocgltd

Sorry, I really misunderstood that. I mistakenly thought that Qt should be used as a GUI toolkit. I don't know which Qt classes you are using in your code, so I can't really help you.

FINAL CUT also has a timer event, and instead of using Qt signals and slots, you can use FINAL CUT signals and callbacks.

Hope this helped a little.

gansm avatar Feb 10 '25 21:02 gansm

The problem I see is that both Qt and FinalCut want to start an event loop. I don't think you can have two simultaneous event loops in the same thread since they compete for grabbing the idle time of the thread. But I'll experiment when I have time

I'll update this thread once I have some experience to share.

ocgltd avatar Feb 10 '25 22:02 ocgltd

@ocgltd I was having a very similar situation in terms of using finalcut that I have a separate event loop

Common solution for those are:

  1. start every event loop in their own thread, then the cross-thread communication could be a problem and it's hard
  2. Replace internal event loop implementation, while finalcut doesn't provide this feature, Qt actually does via QCoreApplication::setEventDispatcher, which means you could implement qt event loop with finalcut/eventloop . However, FApplication is not based on finalcut's eventloop.. I'm not sure why is that, maybe because eventloop class comes later than FApplication. Maybe @gansm has an answer to this. I hope there could an example that Put FApplication and finalcut's event loop together. It means that currently even if you implement a finalcut eventloop based QAbstractEventDispatcher, it doesn't help to put FApplication + QCoreApplication together.
  3. On the other hand, finalcut (or any other TUI library), cares mostly only about stdin, which means you could run Qt's event loop and drive fapplication with QSocketNotifier (monitor stdin file descriptor). I'm not sure if that can make everything in finalcut works, One thing that I noticed is that if only monitor stdin, finalcut's timer may not be working, so you might want to have a timer to run one loop on FApplication regularly, or just use Qt's timer. I've seen other project integrate newt(a C tui lib) with glib main loop with this approach.

I had some success with (3) in the past so you might want to give it try.

wengxt avatar Feb 20 '25 23:02 wengxt

#3 sounds promising, though I'm not too confident about doing that. Would you be able to post the code you used for #3 ?

ocgltd avatar Feb 21 '25 00:02 ocgltd

@ocgltd Very preliminary demo, you can try to drag the window or click the button and it should work.

#include <unistd.h>
#include <QCoreApplication>
#include <QObject>
#include <QSocketNotifier>
#include <final/final.h>

class MyFApp : public finalcut::FApplication {
public:
    using FApplication::FApplication;

private:
    void processExternalUserEvent() override {
        // We actually want to use processNextEvent, but it's not public.
        // processExternalUserEvent is the last function called in
        // processNextEvent So we simply call exitLoop here to use enterLoop to
        // simulate processNextEvent.
        exitLoop();
    }
};

int main(int argc, char *argv[]) {
    // Both will modify argc and consume argv correspondingly anyway.
    // So just leave them as is.
    QCoreApplication qapp(argc, argv);
    MyFApp fapp(argc, argv);

    // examples/hello.cpp
    finalcut::FMessageBox mbox{&fapp};
    const auto nl = finalcut::FString('\n');
    const auto spacing = finalcut::FString(5, ' ');
    const auto line = finalcut::FString(13, L'\U00002500');
    mbox.setText(nl + spacing + "Hello, World!" + spacing + nl + line);
    mbox.setCenterText();
    mbox.show();

    fapp.addCallback("last-dialog-closed", [&qapp]() { qapp.quit(); });

    QSocketNotifier watcher(STDIN_FILENO, QSocketNotifier::Read);
    QObject::connect(&watcher, &QSocketNotifier::activated,
                     [&fapp]() { fapp.enterLoop(); });

    return qapp.exec();
}

wengxt avatar Feb 21 '25 19:02 wengxt

I'm trying to understand your sample. It looks like....you are letting Qt to own the main event loop, and only tell FinalCut to poll for keyboard input once stdin shows a key waiting.

Does the FinalCut refreshing work? (Does it have it's own timers and eventloop that works seperately)?

Uberseer avatar Feb 23 '25 19:02 Uberseer

@Uberseer I don't think that work and yes I do aware of that. I couldn't figure out a public API to get the finalcut's next timeout deadline since it seems to be private internal state, so I just did a stdin pull as only proof of concept.

You can add a QTimer code to drive finalcut's default 200Hz in the a similar way just like the stdin poll, but unless finalcut exposes more internal state, it would be still the partial implementation of fapp's event loop logic.

E.g. we may want to read time_last_event & next_event_wait to set the accurate timeout value to replicate the fapplication's logic. Right now we can only hard code it to match the finalcut's logic.

wengxt avatar Feb 23 '25 19:02 wengxt

As someone new to FinalCut (I just found the project last week) I'm afraid this is over my head. I think I'll stay on the sidelines and maybe the project dev(s) can add something for Qt Compatibility / expose necessary API. From what you describe I would say Qt compatibility is really possible (in a production quality way) at this time.

I may dive in deeper to FinalCut in the future but I'll remain as a lurker for now.

Uberseer avatar Feb 26 '25 01:02 Uberseer