Recent

Author Topic: [SOLVED] Expert needed: how to use 'Application.ShowMainForm:=false' correctly?  (Read 9036 times)

wp

  • Hero Member
  • *****
  • Posts: 12589
Re: Expert needed: how to use 'Application.ShowMainForm:=false' correctly?
« Reply #45 on: December 17, 2024, 06:32:34 pm »
The question now is: does the gtk2 clipboard fail, because it is called or initialized in a wrong way or is there a bug? Is there a workaround for that?
First of all: I have no idea about the inner workings of gtk2...

But following the code with the debugger (with option "-gw3" added in the project options "Additions and overrides") it can be seen that the crash happens in function TGtk2WidgetSet.ClipboardGetOwnership, line "if gtk_selection_owner_set(ClipboardWidget, ...". Inspecting the argument ClipboardWidget reveals an internal field "windows" which is nil when Application.ShowMainform is false, but not nil when Application.ShowMainform is true. This looks suspicious... Trying to find out why this field is nil in the former case, I stepped into Application.Run:
Code: Pascal  [Select][+][-]
  1. procedure TApplication.Run;
  2. begin
  3.   if (FMainForm <> nil) and FShowMainForm then
  4.   begin
  5.     WidgetSet.AppSetupMainForm(FMainForm);
  6.     FMainForm.Show;
  7.   end;
  8.   WidgetSet.AppRun(@RunLoop);
  9. end;
The "window" field in the latter case gets its value in the line "FMainForm.Show". Ah! When ShowMainForm is false this line is not executed at all, and the "window" field remains at nil. Maybe this is the reason? Being no gtk2 expert, I have no idea - gkt2 experts, please forgive me...

But since visibility of the mainform seems to play a crucial role I made the experiment to show the mainform immediately before "Application.ShowMainForm := false" - and now suddenly the error message is gone, and a quick test pasting the clipboard into LibreOffice Draw demonstrates that the picture indeed had arrived in the clipboard.

So, maybe you try the following, counter-intuitive work-around:
Code: Pascal  [Select][+][-]
  1. begin
  2.   RequireDerivedFormResource:=True;
  3.   Application.Scaled := True;
  4.   Application.Initialize;
  5.   Application.CreateForm(TForm1, Form1);
  6.   Form1.Show;
  7.   Application.ShowMainForm:=false;
  8.   Application.Run;
  9. end.  
« Last Edit: December 17, 2024, 06:34:49 pm by wp »

Hartmut

  • Hero Member
  • *****
  • Posts: 891
Re: Expert needed: how to use 'Application.ShowMainForm:=false' correctly?
« Reply #46 on: December 17, 2024, 06:57:33 pm »
Try this one.

Output :
Quote
FormCreate
Application.Run
TForm1.ShowForm2(Data: PtrInt)
TForm1.Visible : FALSE
Form2.ShowModal
Delayed Form1.Show
TForm1.Visible : TRUE
TForm1.FormShow(Sender: TObject) : TForm1 Form1

Thank you BrunoK. I tried it and the result is the same as with the suggestion from wp in reply #34:
The good news is: Application.Run() is now started early enough.
The bad news is: the clipboard on Linux does however not work. Instead it writes the same error message on the console as before.
This is the complete console output:

Code: Text  [Select][+][-]
  1. FormCreate
  2. Application.Run
  3. TForm1.ShowForm2(Data: PtrInt)
  4. TForm1.Visible : FALSE
  5. Form2.ShowModal
  6. (project1:38828): Gtk-CRITICAL **: 17:36:12.936: IA__gtk_selection_owner_set: assertion 'widget == NULL || gtk_widget_get_realized (widget)' failed
  7. Delayed Form1.Show
  8. TForm1.Visible : TRUE
  9. TForm1.FormShow(Sender: TObject) : TForm1 Form1
  10. Application Terminated
  11. Heap dump by heaptrc unit of /hg/utis/project1
  12. ...



I am not an expert, so my response will be based on your example since I am unfamiliar with your real project.

Requirements:
1. The main form should be invisible.
2. The second form must be modal and include a button to copy the image to the clipboard.

Objective:
Create an application compatible with both Windows and Linux Deb64 GTK2.

Additional Condition: Do not use 'Application.ShowMainForm := false'.

Solution: Please see the attached project.

Please don’t judge me too harshly; I spent a little time on this, so I may have overlooked something.

Thank you LV. I tried your demo. The good news is: it works. The not so good news is: this way you change a couple of things "upside down".

I wrote, that I wanted to use feature 'Application.ShowMainForm:=false' in 3 projects. Only for the small benefit to avoid some small flicker at program start. Not more. Assuming that the changes to my 3 existing projects were small (otherwise the effort it's not worth it).

E.g. the current project uses a common unit, which is accessed only via 1 single procedure, which does the complete job and e.g. encapsulates everything, what has to do with a local variable 'Form2'. The reason for that construct is, that I want to use this common unit in an easy way in some other projects too. As a placeholder for this "single procedure" I created procedure Test_Clipboard() in my very 1st post:

Code: Pascal  [Select][+][-]
  1. procedure Test_Clipboard;
  2.    {abbreviation for to call 'Form2' from within 1 procedure}
  3.    var Form2: TForm2;
  4.    begin
  5.    Form2:=TForm2.CreateNew(Application);
  6.  
  7.    Form2.Button1:=TButton.Create(Form2);
  8.    Form2.Button1.Parent:=Form2;
  9.    Form2.Button1.Caption:='Clipboard';
  10.    Form2.Button1.OnClick:=@Form2.Button1Click;
  11.  
  12.    Form2.ShowModal;
  13.    Form2.Free;
  14.    end;

If I want to use your solution, I must disrupt this convenient single procedure. I will check, how much effort this generates and for the 2 other projects the same thing and then decide, whether / in which projects I will implement your solution or not. Please understand that. For future projects, where the Main Form should be hidden at program start, I will incorporate your solution from the beginning, so that it makes no additional effort.

Many thanks again for your help.



So, maybe you try the following, counter-intuitive work-around:
Code: Pascal  [Select][+][-]
  1. begin
  2.   RequireDerivedFormResource:=True;
  3.   Application.Scaled := True;
  4.   Application.Initialize;
  5.   Application.CreateForm(TForm1, Form1);
  6.   Form1.Show;
  7.   Application.ShowMainForm:=false;
  8.   Application.Run;
  9. end.  

Thank you very much wp for your testing, debugging and your new suggestion. This sounds very auspicious! I must stop for today but will test tomorrow and report.

Hartmut

  • Hero Member
  • *****
  • Posts: 891
Re: Expert needed: how to use 'Application.ShowMainForm:=false' correctly?
« Reply #47 on: December 18, 2024, 11:00:21 am »
So, maybe you try the following, counter-intuitive work-around:
Code: Pascal  [Select][+][-]
  1. begin
  2.   RequireDerivedFormResource:=True;
  3.   Application.Scaled := True;
  4.   Application.Initialize;
  5.   Application.CreateForm(TForm1, Form1);
  6.   Form1.Show;
  7.   Application.ShowMainForm:=false;
  8.   Application.Run;
  9. end.  

Thanks again wp for that suggestion. The good news is: the clipboard is now working. The bad news is: I have the same short flicker at startup as before - as without 'Application.ShowMainForm:=false' - so in this constellation this feature unfortunately is useless :-))) It's like bedeviled...

I added 'self.Visible:=false' at 2 places in
Code: Pascal  [Select][+][-]
  1. procedure TForm1.FormCreate(Sender: TObject);
  2. begin
  3.   self.Visible:=false;
  4.   Application.QueueAsyncCall(@ShowForm2, 0);
  5. end;
  6.  
  7. procedure TForm1.ShowForm2(Data: PtrInt); // This is the signature needed by QueueAsyncCall
  8. begin
  9.   self.Visible:=false;
  10.   Unit2.Test_Clipboard; {saves a graphic file into the clipboard}
  11.   Application.Terminate;
  12. end;
  13.  
but that did not help against the flicker.

I disabled 'Form1.Show' and instead inserted 'Application.ShowMainForm:=true' in ShowForm2()
Code: Pascal  [Select][+][-]
  1. begin
  2.   RequireDerivedFormResource:=True;
  3.   Application.Scaled := True;
  4.   Application.Initialize;
  5.   Application.CreateForm(TForm1, Form1);
  6. // Form1.Show;
  7.   Application.ShowMainForm:=false;
  8.   Application.Run;
  9. end.
  10.  
  11. procedure TForm1.ShowForm2(Data: PtrInt); // This is the signature needed by QueueAsyncCall
  12. begin
  13.   Application.ShowMainForm:=true;
  14.   Unit2.Test_Clipboard; {saves a graphic file into the clipboard}
  15.   Application.Terminate;
  16. end;
but then the clipboard did not work any more, which matches your explanations about the "window" field in reply #45.

I changed 'Form1.Show' into
Code: Pascal  [Select][+][-]
  1. begin
  2.   RequireDerivedFormResource:=True;
  3.   Application.Scaled := True;
  4.   Application.Initialize;
  5.   Application.CreateForm(TForm1, Form1);
  6.   Form1.WindowState:=wsMinimized;
  7.   Form1.Show;
  8.   Form1.Hide;
  9.   Application.ShowMainForm:=false;
  10.   Application.Run;
  11. end.
but this did not stop the flicker.

With your description I could find function TGtk2WidgetSet.ClipboardGetOwnership() in <installdir>/lazarus/lcl/interfaces/gtk2/gtk2winapi.inc and found the "window" field in unit 'Gtk2Globals'. It is externally accessible via

Code: Pascal  [Select][+][-]
  1. Gtk2Globals.ClipboardWidget^.window^.parent_instance:=

The code in "FMainForm.Show" where the "window" field is initialized I did not find. Do you think that it is possible to initialize the "window" field somehow manually e.g. by a procedure called before Application.Run()?

wp

  • Hero Member
  • *****
  • Posts: 12589
Re: Expert needed: how to use 'Application.ShowMainForm:=false' correctly?
« Reply #48 on: December 18, 2024, 01:51:53 pm »
Do you think that it is possible to initialize the "window" field somehow manually e.g. by a procedure called before Application.Run()?
With the "-gw3" option in "Additions and overrides" you can use the debugger to follow all the steps into the depths of the LCL until a gtk2 libraray function is called. This way you hopefully find the location where that "window" is created. I tried this, but I was not very patient and was lost along the way. However, I always had the impression that it was related to showing the form.

You could write a bug report and ask for help, maybe there is another way to prepare the clipboard without this "owner". The issue certainly is not related to an incorrect use of Application.ShowMainForm as you were speculating.

Hartmut

  • Hero Member
  • *****
  • Posts: 891
Re: Expert needed: how to use 'Application.ShowMainForm:=false' correctly?
« Reply #49 on: December 18, 2024, 06:55:54 pm »
Do you think that it is possible to initialize the "window" field somehow manually e.g. by a procedure called before Application.Run()?
With the "-gw3" option in "Additions and overrides" you can use the debugger to follow all the steps into the depths of the LCL until a gtk2 libraray function is called. This way you hopefully find the location where that "window" is created. I tried this, but I was not very patient and was lost along the way. However, I always had the impression that it was related to showing the form.

Thanks a lot wp for trying to find that location, where the "window" field is created / filled. I also started the debugger with "-gw3" option and tried to debug Form1.Show(), but I failed. Too many nested calls and I got lost each time. But I'm not very experienced with the debugger and this was my very 1st attempt to debug something inside the LCL.

I could proof that the "window" field is nil before Form1.Show() is called and that this field is filled after that, but I could not find the location, where that's done.

I will write a bug report as you suggested.

If someone else knows or finds out where in Form1.Show() this field
Code: Pascal  [Select][+][-]
  1. Gtk2Globals.ClipboardWidget^.window
is initialized, please let me know.

TRon

  • Hero Member
  • *****
  • Posts: 3930
Re: Expert needed: how to use 'Application.ShowMainForm:=false' correctly?
« Reply #50 on: December 18, 2024, 07:20:28 pm »
@Hartmut: grep the sources and you'll find all sorts of interesting stuff.

f.i. gtkglobals.pp:
Code: Pascal  [Select][+][-]
  1. var
  2.   // All clipboard events are handled by only one widget - the ClipboardWidget
  3.   // This widget is typically the main form
  4.   ClipboardWidget: PGtkWidget;
  5.  

or gtkwidgetset.inc
Code: Pascal  [Select][+][-]
  1. {------------------------------------------------------------------------------
  2.   Function: SetClipboardWidget
  3.   Params: TargetWidget: PGtkWidget - This widget will be connected to all
  4.                   clipboard signals which are all handled by the TGtkWidgetSet
  5.                   itself.
  6.   Returns: none
  7.  
  8.   All supported targets are added to the new widget. This way, no one,
  9.   especially not the lcl, will notice the change. ;)
  10.  ------------------------------------------------------------------------------}
  11. procedure TGtkWidgetSet.SetClipboardWidget(TargetWidget: PGtkWidget);
  12. type
  13.   TGtkTargetSelectionList = record
  14.     Selection: Cardinal;
  15.     List: PGtkTargetList;
  16.   end;
  17.   PGtkTargetSelectionList = ^TGtkTargetSelectionList;
  18. const
  19.   gtk_selection_handler_key: PChar = 'gtk-selection-handlers';
  20.  
  21.   {$IFDEF DEBUG_CLIPBOARD}
  22.   function gtk_selection_target_list_get(Widget: PGtkWidget;
  23.     ClipboardType: TClipboardType): PGtkTargetList;
  24.   var
  25.     SelectionLists, CurSelList: PGList;
  26.     TargetSelList: PGtkTargetSelectionList;
  27.   begin
  28.     SelectionLists := gtk_object_get_data (PGtkObject(Widget),
  29.                                           gtk_selection_handler_key);
  30.     CurSelList := SelectionLists;
  31.     while (CurSelList<>nil) do begin
  32.       TargetSelList := CurSelList^.Data;
  33.       if (TargetSelList^.Selection = ClipboardTypeAtoms[ClipboardType]) then
  34.       begin
  35.         Result:=TargetSelList^.List;
  36.         exit;
  37.       end;
  38.       CurSelList := CurSelList^.Next;
  39.     end;
  40.     Result:=nil;
  41.   end;
  42.  
  43.   procedure WriteTargetLists(Widget: PGtkWidget);
  44.   var c: TClipboardType;
  45.     TargetList: PGtkTargetList;
  46.     TmpList: PGList;
  47.     Pair: PGtkTargetPair;
  48.   begin
  49.     DebugLn('  WriteTargetLists WWW START');
  50.     for c:=Low(TClipboardType) to High(TClipboardType) do begin
  51.       TargetList:=gtk_selection_target_list_get(Widget,c);
  52.       DebugLn('  WriteTargetLists WWW ',ClipboardTypeName[c],' ',dbgs(TargetList<>nil));
  53.       if TargetList<>nil then begin
  54.         TmpList:=TargetList^.List;
  55.         while TmpList<>nil do begin
  56.           Pair:=PGtkTargetPair(TmpList^.Data);
  57.           DebugLn('    WriteTargetLists BBB ',dbgs(Pair^.Target),' ',GdkAtomToStr(Pair^.Target));
  58.           TmpList:=TmpList^.Next;
  59.         end;
  60.       end;
  61.     end;
  62.     DebugLn('  WriteTargetLists WWW END');
  63.   end;
  64.   {$ENDIF}
  65.  
  66.   procedure ClearTargetLists(Widget: PGtkWidget);
  67.   // MG: Reading in gtk internals is dirty, but there seems to be no other way
  68.   //     to clear the old target lists
  69.   var
  70.     SelectionLists: PGList;
  71.     {$ifdef gtk1}
  72.     CurSelList: PGList;
  73.     TargetSelList: PGtkTargetSelectionList;
  74.     {$else}
  75.     CurClipboard: TClipboardType;
  76.     {$endif}
  77.   begin
  78.     {$IFDEF DEBUG_CLIPBOARD}
  79.     DebugLn('  ClearTargetLists WWW START');
  80.     {$ENDIF}
  81.     {$ifdef gtk1}
  82.     SelectionLists := gtk_object_get_data (PGtkObject(Widget),
  83.       gtk_selection_handler_key);
  84.     CurSelList := SelectionLists;
  85.     while (CurSelList<>nil) do
  86.     begin
  87.       TargetSelList := CurSelList^.Data;
  88.       gtk_target_list_unref(TargetSelList^.List);
  89.       g_free(TargetSelList);
  90.       CurSelList := CurSelList^.Next;
  91.     end;
  92.     g_list_free(SelectionLists);
  93.     {$else}
  94.     // clear 3 selections
  95.     for CurClipboard := Low(TClipboardType) to High(CurClipboard) do
  96.       gtk_selection_clear_targets(Widget, ClipboardTypeAtoms[CurClipboard]);
  97.  
  98.     SelectionLists := gtk_object_get_data(PGtkObject(Widget),
  99.       gtk_selection_handler_key);
  100.     if SelectionLists <> nil then
  101.       g_list_free(SelectionLists);
  102.     {$endif}
  103.     gtk_object_set_data (PGtkObject(Widget), gtk_selection_handler_key, GtkNil);
  104.     {$IFDEF DEBUG_CLIPBOARD}
  105.     DebugLn('  ClearTargetLists WWW END');
  106.     {$ENDIF}
  107.   end;
  108.  
  109. var c: TClipboardType;
  110. begin
  111.   if ClipboardWidget=TargetWidget then exit;
  112.   {$IFDEF DEBUG_CLIPBOARD}
  113.   DebugLn('[TGtkWidgetSet.SetClipboardWidget] ',dbgs(ClipboardWidget<>nil),' -> ',dbgs(TargetWidget<>nil),' ',GetWidgetDebugReport(TargetWidget));
  114.   {$ENDIF}
  115.   if ClipboardWidget<>nil then begin
  116.     {$IFDEF DEBUG_CLIPBOARD}
  117.     WriteTargetLists(ClipboardWidget);
  118.     {$ENDIF}
  119.     ClearTargetLists(ClipboardWidget);
  120.     {$IFDEF DEBUG_CLIPBOARD}
  121.     WriteTargetLists(ClipboardWidget);
  122.     {$ENDIF}
  123.   end;
  124.  
  125.   ClipboardWidget:=TargetWidget;
  126.   if ClipboardWidget<>nil then begin
  127.     // connect widget to all clipboard signals
  128.     g_signal_connect(PGtkObject(ClipboardWidget),'selection_received',
  129.       TGTKSignalFunc(@ClipboardSelectionReceivedHandler),GtkNil);
  130.     g_signal_connect(PGtkObject(ClipboardWidget),'selection_get',
  131.       TGTKSignalFunc(@ClipboardSelectionRequestHandler),GtkNil);
  132.     g_signal_connect(PGtkObject(ClipboardWidget),'selection_clear_event',
  133.       TGTKSignalFunc(@ClipboardSelectionLostOwnershipHandler),GtkNil);
  134.     // add all supported targets for all clipboard types
  135.     for c:=Low(TClipboardType) to High(TClipboardType) do begin
  136.       if (ClipboardTargetEntries[c]<>nil) then begin
  137.         //DebugLn('TGtkWidgetSet.SetClipboardWidget ',GdkAtomToStr(ClipboardTypeAtoms[c]),' Entries=',dbgs(ClipboardTargetEntryCnt[c]));
  138.         gtk_selection_add_targets(ClipboardWidget,ClipboardTypeAtoms[c],
  139.                   ClipboardTargetEntries[c],ClipboardTargetEntryCnt[c]);
  140.       end;
  141.     end;
  142.     {$IFDEF DEBUG_CLIPBOARD}
  143.     WriteTargetLists(ClipboardWidget);
  144.     {$ENDIF}
  145.   end;
  146. end;
  147.  
« Last Edit: December 18, 2024, 07:24:18 pm by TRon »
I do not have to remember anything anymore thanks to total-recall.

LV

  • Full Member
  • ***
  • Posts: 204
Re: Expert needed: how to use 'Application.ShowMainForm:=false' correctly?
« Reply #51 on: December 18, 2024, 09:48:50 pm »
@Hartmut. The good news is that many of the suggestions have worked. I'm sorry to inform you, but here's another news: let's set aside your example piece and focus on creating a simple project that features one main form on a Linux GTK2 system. However, occasional flickering may occur here as well.

Hartmut

  • Hero Member
  • *****
  • Posts: 891
Re: Expert needed: how to use 'Application.ShowMainForm:=false' correctly?
« Reply #52 on: December 19, 2024, 12:30:16 pm »
@Hartmut. The good news is that many of the suggestions have worked. I'm sorry to inform you, but here's another news: let's set aside your example piece and focus on creating a simple project that features one main form on a Linux GTK2 system. However, occasional flickering may occur here as well.

If I acceppt this flicker then I can stay with the solution which I already had, before I tried to use feature 'Application.ShowMainForm:=false'. In this case there is no need to create something new.



@Hartmut: grep the sources and you'll find all sorts of interesting stuff.

I did this already before. I tried to find where field 'Gtk2Globals.ClipboardWidget^.window' is initializied. When I searched for 'ClipboardWidget' or for 'window' then I got so many matches that it was impossible to inspect them all. When I searched for 'ClipboardWidget^.window' then there were no matches.

I had a look at procedure TGtkWidgetSet.SetClipboardWidget() from <installdir>/lazarus/lcl/interfaces/gtk/gtkwidgetset.inc which you posted. I understand nothing from what's going on there, this is far beyond my horizon. I searched the whole procedure for a 'window' field but it does not occur. What did you mean?

LV

  • Full Member
  • ***
  • Posts: 204
Re: Expert needed: how to use 'Application.ShowMainForm:=false' correctly?
« Reply #53 on: December 19, 2024, 02:37:08 pm »
As @wp suggested, You could write a bug report and ask for help.
By the way, there is already something about this.
https://gitlab.com/freepascal.org/lazarus/lazarus/-/issues/40487 :-[

Hartmut

  • Hero Member
  • *****
  • Posts: 891
Re: Expert needed: how to use 'Application.ShowMainForm:=false' correctly?
« Reply #54 on: December 19, 2024, 07:16:52 pm »
As @wp suggested, You could write a bug report and ask for help.
By the way, there is already something about this.
https://gitlab.com/freepascal.org/lazarus/lazarus/-/issues/40487 :-[

As I wrote yesterday in reply #49, I will write a bug report (as soon as I had time to create a new demo, which is much shorter than the one in my 1st post in this Topic).

I checked issue 40487 but this is about a different type of flicker.

Hartmut

  • Hero Member
  • *****
  • Posts: 891
Re: Expert needed: how to use 'Application.ShowMainForm:=false' correctly?
« Reply #55 on: December 21, 2024, 04:40:22 pm »
As recommended I filed a bug report in https://gitlab.com/freepascal.org/lazarus/lazarus/-/issues/41286

Because this Topic got some longer I will give a short summary:

A solution which uses feature 'Application.ShowMainForm:=false' together with a working clipboard on Linux was not possible due to above bug.

But the following 2 solutions were found:
 - if you only want to use 'Application.ShowMainForm:=false' but without the clipboard on Linux, then you can use the solution from wp with Application.QueueAsyncCall() in reply #34
 - if you want to use the clipboard on Linux and avoid the flickering of Form1 without using feature 'Application.ShowMainForm:=false' then you can use the solution from LV in reply #43.

Again many thanks to all who helped me.

 

TinyPortal © 2005-2018