Recent

Author Topic: [SOLVED] Taking a screenshot on Linux fails w. Wayland windowmanager (X11 works)  (Read 1108 times)

Hartmut

  • Hero Member
  • *****
  • Posts: 1131
I was in doubt if subforum "Programming / Operating Systems / Linux" might be better - I hope I picked the best one. Otherwise please move. Thanks.

I use 2 different procedures to take a screenshot of a complete monitor. Both work perfectly on Linux Ubuntu 24.04 with Gnome Desktop and X11 window manager. But if I switch to Wayland window manager (on the same OS and the same computer), both procedures fail. This happens with Lazarus 2.0.10 / FPC 3.2.0 and with Lazarus 3.4.0 / FPC 3.2.2 (both with GTK2).

I want to improve my 2 procedures so that they work with Wayland too. What must I do?
Switching to BGRABitmap is not an option in this particular project.

Here are my 2 procedures (where I added some debug outputs):

Code: Pascal  [Select][+][-]
  1. function getScreenDC: HDC;
  2.    {helper: returns the Handle for the Device Context (DC) of the Desktop-Window}
  3.    var h: HDC; {= THandle}
  4.    begin
  5.    h:=GetDC(0); {returns the handle of a Device Context (DC) for the client area of a window}
  6.    if h <> 0 then exit(h); {if OK}
  7.  
  8.    writeln('Getting Handle for the DC of the Desktop-Window failed!');
  9.    halt; {this never occured}
  10.    end; {getScreenDC}
  11.  
  12. procedure saveScreenshotA(filespec: string);
  13.    {stores a screenshot of all monitors into a PNG-file}
  14.    const DEB: bool = True; {show Debug-Infos?}
  15.    var PNG: TPortableNetworkGraphic;
  16.        ScreenDC: HDC; {= THandle}
  17.    begin
  18.    ScreenDC:=getScreenDC; {get Handle for the Device Context of the Desktop-Window}
  19. if DEB then writeln('Handle=', ScreenDC);
  20.    PNG:=TPortableNetworkGraphic.Create;
  21.    try
  22. if DEB then writeln('AAA');
  23.       PNG.LoadFromDevice(ScreenDC); {takes the screenshot}
  24. if DEB then writeln('BBB');
  25.       PNG.SaveToFile(filespec);
  26. if DEB then writeln('CCC');
  27.    finally
  28.       PNG.Free;
  29.       ReleaseDC(0,ScreenDC);
  30. if DEB then writeln('DDD');
  31.    end; {try}
  32.    end; {saveScreenshotA}
  33.  
  34. procedure saveScreenShotB(filespec: string);
  35.    {stores a screenshot of the primary monitor into a PNG-file. Notes for below code:
  36.     (1) fixes a bug in Lazarus 2.0.10 + 2.2.4, that command 'RD:=PNG.Canvas.ClipRect'
  37.         returns 2x 2000 because of a not initialized Handle of the Canvas.
  38.     (2) Linux only: without this fix the resulting PNG-file would be completely black}
  39.  
  40.    procedure show_TRect(R: TRect; msg: string = '');
  41.       {displays the content of a 'TRect'}
  42.       begin
  43.       if msg <> '' then write(msg);
  44.       writeln('X1=', R.Left, ' Y1=', R.Top, ' X2=', R.Right, ' Y2=', R.Bottom,
  45.               ' b=', R.Width, ' h=', R.Height);
  46.       end;
  47.  
  48.    const DEB: bool = True; {show Debug-Infos?}
  49.    var PNG: TPortableNetworkGraphic;
  50.        M: TMonitor;
  51.        CVS: TCanvas; {canvas of the screen}
  52.        RS,RD: TRect;
  53.        w,h: integer;
  54.    begin
  55.    M:=Screen.PrimaryMonitor; {monitor to use}
  56.    CVS:=TCanvas.Create;      {canvas of the screen}
  57.  
  58.    try
  59.       CVS.Handle:=getScreenDC; {get Handle for the Device Context of the Desktop-Window}
  60. if DEB then writeln('Handle=', CVS.Handle);
  61.       PNG:=TPortableNetworkGraphic.Create;
  62.       try
  63. if DEB then writeln('PixelFormat=', PNG.PixelFormat);
  64.          PNG.PixelFormat:=pf24bit;
  65.          w:=M.Width;                    {get size of wanted monitor: }
  66.          h:=M.Height;
  67. if DEB then writeln('w=', w, ' h=', h); {=> w=1280 h=1024}
  68. if DEB then writeln('PixelsPerInch=', M.PixelsPerInch); {=> 96}
  69.          RS:=M.BoundsRect;              {RS=Source-Rect}
  70. if DEB then show_TRect(RS, 'RS: ');     {=> X1=0 Y1=0 X2=1280 Y2=1024}
  71.  
  72. if DEB then writeln('AAA');
  73.          PNG.SetSize(w,h);              {set size of PNG}
  74. if DEB then writeln('BBB');
  75.  
  76.          PNG.Canvas.CopyMode:=cmSrcCopy;
  77.          PNG.Canvas.Pixels[0,0]:=0;     {fixes a bug, see above in (1)}
  78. if DEB then writeln('CCC');
  79.          RD:=PNG.Canvas.ClipRect;       {RD=Destination-Rect}
  80. if DEB then show_TRect(RD, 'RD: ');
  81.  
  82. {$IFDEF LINUX}
  83.          inc(RD.Bottom); {fixes a bug only on Linux, see above in (2)}
  84. if DEB then show_TRect(RD, 'RD: ');
  85. {$ENDIF}
  86.              {copy a rectangular from 'RS' to 'RD': }
  87. if DEB then writeln('DDD');
  88.          PNG.Canvas.CopyRect(RD,CVS,RS); {Destination,Source-Canvas,Source}
  89. if DEB then writeln('EEE');
  90.          PNG.SaveToFile(filespec);
  91. if DEB then writeln('FFF');
  92.  
  93.       finally
  94.          PNG.Free;
  95.          ReleaseDC(0,CVS.Handle);
  96.       end; {try2}
  97.    finally
  98.       CVS.Free;
  99.    end; {try1}
  100.    end; {saveScreenShotB}

As said, with X11 both procedures work perfectly.
But with Wayland window manager I face this problems:

Procedure saveScreenshotA() throws a window with this error message (see screenshot1):
   Failed to get raw image from device.
   Press OK to ignore and risk data corruption.
   Press Abort to kill the program.

and breaks without storing a file. The debug output shows:
Code: Text  [Select][+][-]
  1. Handle=123608961461312
  2. AAA
  3. WARNING: TGtk2WidgetSet.RawImage_FromDrawable: gdk_image_get failed
  4. DDD
which means, that procedure PNG.LoadFromDevice(ScreenDC) throws this error.

Procedure saveScreenShotB() generates no Error, but the stored file contains garbage (see screenshot2). Instead of a screenshot, it contains 12 times the program's icon and the rest is completely black. The debug output shows:
Code: Text  [Select][+][-]
  1. Handle=126870986107456
  2. PixelFormat=pfDevice
  3. w=1280 h=1024
  4. PixelsPerInch=96
  5. RS: X1=0 Y1=0 X2=1280 Y2=1024 b=1280 h=1024
  6. AAA
  7. BBB
  8. CCC
  9. RD: X1=0 Y1=0 X2=1280 Y2=1024 b=1280 h=1024
  10. RD: X1=0 Y1=0 X2=1280 Y2=1025 b=1280 h=1025
  11. DDD
  12. EEE
  13. FFF

Please how can I improve my 2 procedures so that they work with Wayland too? Thanks in advance.
« Last Edit: June 28, 2025, 06:17:23 pm by Hartmut »

rdxdt

  • New member
  • *
  • Posts: 9
Wayland screen capture must be done through pipewire due to the security provided by the Wayland protocol so one unauthorized application can't snoop at the screen/ other windows.
Applications must explicitly request the user to grant them access to a screen or window. This is done through xdg-desktop-portal.
The actual window content is delivered through PipeWire in the form of a video stream. To access it, an application has to connect to this stream and negotiate a format with the stream source.

Hartmut

  • Hero Member
  • *****
  • Posts: 1131
Thanks a lot rdxdt for this valuable infos. It's not good news for me (but this is not your fault).

I understood, that a program which wants to take a screenshot has to use pipewire to get the screenshot as a stream. I'd never heard about pipewire before but I found it in https://pipewire.org. Seems to be not so easy to use it for this purpose...

xdg-desktop-portal I don't know too. I found it in https://wiki.archlinux.org/title/XDG_Desktop_Portal. Seems that it has to be installed for the currently used 'backend' (which seems to be the same as the currently used 'Desktop' like GNOME or KDE-Plasma).

Applications must explicitly request the user to grant them access to a screen or window.
I don't understand this sentence:
 - which application must request something? The application which wants to take a screenshot or the application which draws something on the screen (which then shall be captured)?
 - 'the user' is the currently logged in user?
 - and this 'to grant access' is done only once in xdg-desktop-portal and will then work for the future until this access is removed some day?

Sorry for my questions, but this all is completely new for me.

PascalDragon

  • Hero Member
  • *****
  • Posts: 6381
  • Compiler Developer
Applications must explicitly request the user to grant them access to a screen or window.
I don't understand this sentence:
 - which application must request something? The application which wants to take a screenshot or the application which draws something on the screen (which then shall be captured)?

The application that want to capture something. The desktop environment will usually pop up some dialog saying that application XYZ wants to capture the desktop and whether this should be allowed.

- 'the user' is the currently logged in user?

Correct.

- and this 'to grant access' is done only once in xdg-desktop-portal and will then work for the future until this access is removed some day?

It depends on what the user selects. The popup dialog often contains an option to grant this access only once or permanently.

Fred vS

  • Hero Member
  • *****
  • Posts: 3873
    • StrumPract is the musicians best friend
It depends on what the user selects. The popup dialog often contains an option to grant this access only once or permanently.

This is exactly what has been integrated into XLibre with its Xnamespace concept to have the same level of security as Wayland (but ready to use and, of course, with the possibility of accessing it once or permanently).
I use Lazarus 2.2.0 32/64 and FPC 3.2.2 32/64 on Debian 11 64 bit, Windows 10, Windows 7 32/64, Windows XP 32,  FreeBSD 64.
Widgetset: fpGUI, MSEgui, Win32, GTK2, Qt.

https://github.com/fredvs
https://gitlab.com/fredvs
https://codeberg.org/fredvs

Hartmut

  • Hero Member
  • *****
  • Posts: 1131
Thanks a lot PascalDragon for that explanations. Now I have a rough imagination how pipewire and xdg-desktop-portal have to be used together. Seems to be a lot of work to get this all running. I must move this project into the future because for the next time I have enough other duties.

This is exactly what has been integrated into XLibre with its Xnamespace concept to have the same level of security as Wayland
I read that Xlibre is a fork of the Xorg Xserver. So it seems to be an alternative to Wayland. But in my case I'm looking explicitly for a solution for Wayland.

If someone shall implement a solution in FPC for pipewire (and is willing to share this here) I would be very interested.

Thaddy

  • Hero Member
  • *****
  • Posts: 18961
  • Glad to be alive.
Well the answer is already explained and even step-by-step. It will never be as easy as taking a handle and saving the buffer anymore. Even with X-fork.
Recovered from removal of tumor in tongue following tongue reconstruction with a part from my leg.

 

TinyPortal © 2005-2018