Recent

Author Topic: Remote desktop software in Lazarus  (Read 3453 times)

Ericktux

  • Sr. Member
  • ****
  • Posts: 394
    • ericksystem software
Remote desktop software in Lazarus
« on: April 09, 2026, 07:42:59 pm »
Remote desktop software in Lazarus  :)

Hello friends, greetings! I'm here with some news. Look, I found this remote desktop + chat + file sharing project for Delphi:

https://github.com/maickonn/AllaKore_Remote

https://github.com/OneideLuizSchneider/AllaKore_Remote

I've successfully ported it to Lazarus and it works. I'm currently using Synapse + TCP (Indy gave me problems) and RichMemo (for chat). I'm 90% complete
* Screen mirroring works
* Mouse works
* Keyboard works
* User Account Control (UAC) works
* Resizing works
* Chat works
* File sharing works, but it needs improvement.

I'd like this to be a community project for Lazarus Pascal.

I'm currently having a problem  :( I can't seem to solve: the screenshot transmission (JPG) is slow at high resolutions like 1920x1080, but it works fine at lower resolutions like 800x600. I need your help to figure out what I'm doing wrong. Perhaps I should switch from TCP to UDP, or maybe use H.264 video streaming instead of screenshots.

I'm also suspecting the "server" or the image compression. I've also integrated Dirty Delta Region into the program.

The program works as both a server and a client; the client functions as both a PC-controller and a PC-controlled.

I'm sharing this project with you so we can improve it and solve this problem. It works on Windows 7, Windows 8, Windows 10, and Windows 11.

Client path:
lazarus_remote_desktop\Source\Client
Server path:
lazarus_remote_desktop\Source\Server

Greetings from Peru

https://www.mediafire.com/file/oo6mndlx532t00j/lazarus_remote_desktop.zip/file
.
« Last Edit: April 09, 2026, 07:48:03 pm by Ericktux »
I love desktop software
https://www.ericksystem.com

creaothceann

  • Sr. Member
  • ****
  • Posts: 361
Re: Remote desktop software in Lazarus
« Reply #1 on: April 09, 2026, 10:10:49 pm »
Slowdown could be caused by compression/decompression overhead, or network overhead. You could implement showing statistics (xxxx.x milliseconds) for both.

EDIT: Make sure to use a high-resolution timer.


You could take a look at Parsec's features. It uses bandwidth limits, software / hardware decoders, Direct3D / Vulkan for rendering, constant FPS (higher quality) mode, statistics display, compressed (Opus) or uncompressed audio, and clipboard sharing. The only thing missing (imo) is file sharing.
« Last Edit: April 09, 2026, 10:34:05 pm by creaothceann »

Ericktux

  • Sr. Member
  • ****
  • Posts: 394
    • ericksystem software
Re: Remote desktop software in Lazarus
« Reply #2 on: April 09, 2026, 11:33:27 pm »
Hi friend, thanks for your reply. I've tried using Mormot2 to compress images, but it's still just as slow.
I love desktop software
https://www.ericksystem.com

creaothceann

  • Sr. Member
  • ****
  • Posts: 361
Re: Remote desktop software in Lazarus
« Reply #3 on: April 10, 2026, 12:45:53 am »
You have 5 operations: screen capture, compression (optional), transfer, decompression, and display.

- Screen capture could be potentially sped up via a hardware-accelerated method.
- Perhaps the compression level could be reduced to speed it up. If not you could try e.g. a zlib library. At some point the faster compression speed will be balanced out by the resulting larger transfer speed.
- A very low latency mode (that uses UDP) could send out new blocks of data without waiting for the receiver's confirmation.
- Display could also be potentially sped up via a hardware-accelerated method.
« Last Edit: April 10, 2026, 06:37:13 am by creaothceann »

Ericktux

  • Sr. Member
  • ****
  • Posts: 394
    • ericksystem software
Re: Remote desktop software in Lazarus
« Reply #4 on: April 10, 2026, 02:35:42 am »
Thank you very much for your response, friend. Do you know of any method or code to activate hardware acceleration in Lazarus Pascal?
I love desktop software
https://www.ericksystem.com

creaothceann

  • Sr. Member
  • ****
  • Posts: 361
Re: Remote desktop software in Lazarus
« Reply #5 on: April 10, 2026, 08:10:56 am »
No, unfortunately I don't.

You'd probably have to use the SDKs of Nvidia / AMD / Intel.

rvk

  • Hero Member
  • *****
  • Posts: 7018
Re: Remote desktop software in Lazarus
« Reply #6 on: April 10, 2026, 10:45:54 am »
Hardware acceleration should be the last step because it could be different for every computer. And often it even needs a separate screen-driver. See VNC (and clones).

You also might want to look in the other techniques done by VNC and other RDP protocols.

Like using CopyRect Encoding etc. (in which only changes are transmitted to the other side).

https://www.rfc-editor.org/rfc/rfc6143.html
https://web.mit.edu/cdsdev/src/howitworks.html

But you can also go a completely different route and look at how Rustdesk does this.
(as I understand) it does this by treating the complete screen as video. It gets its captures from scrap library (for example) and determines what the relevant parts are via video-encoding (through AV1/VP9/VP8 in software or H.264 (AVC)/H.265 (HEVC) in hardware encoding). This is much more efficient over low bandwidth than CopyRect or any encoding VNC (clones) can reach.

gidesa

  • Full Member
  • ***
  • Posts: 248
Re: Remote desktop software in Lazarus
« Reply #7 on: April 10, 2026, 12:09:34 pm »
Sending a stream of Jpg images is similar to Http using Mjpg. Indeed you have an in-frame compression (Jpg), but you don't have any inter-frame compression between different frames. And using Tcp is surely slower than using Udp.
So, at first, you can try to use Udp, but this requires to create your customized protocol, with some sort of error correction.
Then you can use a more advanced video stream, as H264, that has inter-frame compression.
Of course speed strict depends from communication line and hardware performance. That is, maybe a fast line with recent hardware could have good performance also at 1920x1080.
An interesting advanced feature could be an auto-tuning included in program: use software compression/decompression as default, and hardware acceleration when present. I see that for now it's only for Windows, then using H264 with hardware acceleration could be simpler.

EDIT: reading https://github.com/maickonn/AllaKore_Remote, seems that it has optimizations:
"- Remote access with RFB algorithm (Send only what has changed on the screen)."
" -Data Compression (zLib)."






 
« Last Edit: April 10, 2026, 12:12:25 pm by gidesa »

Ericktux

  • Sr. Member
  • ****
  • Posts: 394
    • ericksystem software
Re: Remote desktop software in Lazarus
« Reply #8 on: April 10, 2026, 02:20:13 pm »
Thank you so much for your replies, friends. I have a question: how can I use H.264 for video streaming in Lazarus Pascal? I've heard it's the fastest way to stream screens.
As I mentioned, I'm about 90% of the way through the program; the only thing I need to fix is ​​the slowness when moving windows. I was thinking of using UDP, as you mentioned it's faster than TCP.

Currently, this is the screen capture sender (client):

Code: Pascal  [Select][+][-]
  1. procedure TScreenShareThread.Execute;
  2. var
  3.   bmp: TBitmap;
  4.   jpg: TJPEGImage;
  5.   ms: TMemoryStream;
  6.   Data: RawByteString;
  7.   fpsTimer: Int64;
  8.   ScreenDC: HDC;
  9.   hDesktop: HDESK;
  10.   InitialFrames, BmpWidth: Integer;
  11.   by, bx, py: Integer;
  12.   BlocksX, BlocksY: Integer;
  13.   TargetMs: Integer;
  14.   BlockBmp: TBitmap;
  15.   BlockJpg: TJPEGImage;
  16.   BlockMs: TMemoryStream;
  17.   BlockChanged: Boolean;
  18.   ChangedCount: Integer;
  19.   BlockSize32: LongWord;
  20.   BlockBytes, FinalPacket: RawByteString;
  21.   w, h: Integer;
  22.   PrevData: array of Byte;
  23.   CurRow: PByteArray;
  24.   PrevRow: PByte;
  25.   RowBytes: Integer;
  26.  
  27.     ry: Integer;
  28.   BlockRow, SrcRow: PByteArray;
  29.     BlockStride: Integer;
  30.  
  31.     BlockRowBytes: Integer;
  32.  
  33.     BytesPerPixel: Integer;
  34.  
  35.     FinalStream: TMemoryStream;
  36.  
  37. const
  38.   BLOCK_SIZE = 64;
  39. begin
  40.   InitialFrames := 0;
  41.   BlockBmp  := TBitmap.Create;
  42.   BlockJpg  := TJPEGImage.Create;
  43.   BlockMs   := TMemoryStream.Create;
  44.  
  45.         bmp := TBitmap.Create;
  46.       bmp.PixelFormat := pf32bit;
  47.       jpg := TJPEGImage.Create;
  48.       ms  := TMemoryStream.Create;
  49.  
  50.       FinalStream := TMemoryStream.Create;
  51.  
  52.  
  53.   try
  54.     while not Terminated do
  55.     begin
  56.       hDesktop := OpenInputDesktop(0, True, MAXIMUM_ALLOWED);
  57.       if hDesktop <> 0 then
  58.       begin
  59.         SetThreadDesktop(hDesktop);
  60.         CloseDesktop(hDesktop);
  61.       end;
  62.       if FSock = INVALID_SOCKET then begin Sleep(300); Continue; end;
  63.  
  64.       fpsTimer := GetTickCount64;
  65.       {bmp := TBitmap.Create;
  66.       bmp.PixelFormat := pf32bit;
  67.       jpg := TJPEGImage.Create;
  68.       ms  := TMemoryStream.Create;}
  69.       try
  70.         ScreenDC := GetDC(GetDesktopWindow);
  71.         if ScreenDC = 0 then begin Sleep(50); Continue; end;
  72.         try
  73.           bmp.LoadFromDevice(ScreenDC);
  74.         finally
  75.           ReleaseDC(GetDesktopWindow, ScreenDC);
  76.         end;
  77.         if (bmp.Width = 0) or (bmp.Height = 0) then begin Sleep(50); Continue; end;
  78.  
  79.  
  80.           {
  81.           if bmp.PixelFormat <> pf24bit then
  82.           bmp.PixelFormat := pf24bit;
  83.           }
  84.  
  85.  
  86.  
  87.         BmpWidth := bmp.Width;
  88.         {RowBytes := bmp.Width * 3;
  89.         }
  90.  
  91.           // Calcular stride REAL usando dos ScanLines consecutivos
  92.   // En lugar de asumir Width*3, medir la distancia real en memoria
  93.  {
  94.   if bmp.Height > 1 then
  95.     RowBytes := Abs(PByte(bmp.ScanLine[1]) - PByte(bmp.ScanLine[0]))
  96.   else
  97.     RowBytes := ((bmp.Width * 3) + 3) and not 3;  // alinear a 4 bytes
  98.   }
  99.   RowBytes := bmp.Width * 4;  // pf32bit = siempre exacto, sin padding
  100. BytesPerPixel := 4;         // fijo, no calcular más
  101.  
  102.  
  103.         // ── FRAME COMPLETO: buffer no inicializado, resolución cambió,
  104.         //    o primeros 10 frames para garantizar que llegue al receptor ──
  105.         if (Length(PrevData) <> bmp.Height * RowBytes) or
  106.            (InitialFrames < 10) then
  107.         begin
  108.           // Inicializar/redimensionar buffer si necesario
  109.           if Length(PrevData) <> bmp.Height * RowBytes then
  110.             SetLength(PrevData, bmp.Height * RowBytes);
  111.  
  112.           ms.Clear;
  113.           jpg.Assign(bmp);
  114.           jpg.CompressionQuality := 45;  // calidad de comprension
  115.           jpg.SaveToStream(ms);
  116.  
  117.           SetLength(Data, ms.Size + 1);
  118.           Data[1] := AnsiChar($FF);  // frame completo
  119.           Move(ms.Memory^, Data[2], ms.Size);
  120.           WriteInt64BigEndian(FSock, Length(Data));
  121.           Send(FSock, @Data[1], Length(Data), 0);
  122.  
  123.           // Copiar frame actual al buffer
  124.           for py := 0 to bmp.Height - 1 do
  125.           begin
  126.             CurRow := bmp.ScanLine[py];
  127.             Move(CurRow^[0], PrevData[py * RowBytes], RowBytes);
  128.           end;
  129.  
  130.           if InitialFrames < 10 then Inc(InitialFrames);
  131.         end
  132.         else
  133.         begin
  134.           // ── MODO DELTA: solo bloques que cambiaron ──
  135.           FinalStream.Clear;  // limpiar para el siguiente frame
  136.           BlocksX := (bmp.Width  + BLOCK_SIZE - 1) div BLOCK_SIZE;
  137.           BlocksY := (bmp.Height + BLOCK_SIZE - 1) div BLOCK_SIZE;
  138.  
  139.           FinalPacket  := '';
  140.           ChangedCount := 0;
  141.           BlockBmp.PixelFormat := pf32bit;
  142.  
  143.           for by := 0 to BlocksY - 1 do
  144.           for bx := 0 to BlocksX - 1 do
  145.           begin
  146.             // Comparar bloque usando buffer propio (sin problemas de stride)
  147.             BlockChanged := False;
  148.             for py := by * BLOCK_SIZE to
  149.                       Min((by+1)*BLOCK_SIZE - 1, bmp.Height - 1) do
  150.             begin
  151.               if BlockChanged then Break;
  152.               CurRow  := bmp.ScanLine[py];
  153.               //PrevRow := @PrevData[py * RowBytes + bx * BLOCK_SIZE * 3];
  154.               PrevRow := @PrevData[py * RowBytes + bx * BLOCK_SIZE * (RowBytes div bmp.Width)];
  155.  
  156.  
  157.  
  158.               //BytesPerPixel := RowBytes div bmp.Width;  // calcular una vez fuera del loop
  159.  
  160.  
  161.  
  162.               if not CompareMem(
  163.                 @CurRow^[bx * BLOCK_SIZE * BytesPerPixel],
  164.                 PrevRow,
  165.                 Min(BLOCK_SIZE, bmp.Width - bx * BLOCK_SIZE) * BytesPerPixel) then
  166.                 BlockChanged := True;
  167.             end;
  168.  
  169.             if not BlockChanged then Continue;
  170.  
  171.             // Extraer bloque como sub-bitmap
  172.             w := Min(BLOCK_SIZE, bmp.Width  - bx * BLOCK_SIZE);
  173.             h := Min(BLOCK_SIZE, bmp.Height - by * BLOCK_SIZE);
  174.  
  175.  
  176.  
  177.                         // CAMBIO: recrear BlockBmp limpio en cada bloque
  178.             if (BlockBmp.Width <> w) or (BlockBmp.Height <> h) then
  179.             begin
  180.               BlockBmp.PixelFormat := pf32bit;
  181.               BlockBmp.SetSize(w, h);
  182.             end;
  183.  
  184.  
  185.  
  186.  
  187.             // Calcular stride real del BlockBmp (no asumir w*3)
  188.             {
  189.             if BlockBmp.Height > 1 then
  190.               BlockRowBytes := Abs(PByte(BlockBmp.ScanLine[1]) - PByte(BlockBmp.ScanLine[0]))
  191.             else
  192.               BlockRowBytes := ((w * 3) + 3) and not 3;
  193.             }
  194.  
  195.  
  196.  
  197.             for ry := 0 to h - 1 do
  198.             begin
  199.               SrcRow   := bmp.ScanLine[by * BLOCK_SIZE + ry];
  200.               BlockRow := BlockBmp.ScanLine[ry];
  201.               // Usar w*3 bytes útiles — el padding de BlockBmp queda en cero, no importa
  202.               Move(SrcRow^[bx * BLOCK_SIZE * 4], BlockRow^[0], w * 4);
  203.  
  204.             end;
  205.  
  206.  
  207.  
  208.             {
  209.             BitBlt(
  210.             BlockBmp.Canvas.Handle, 0, 0, w, h,
  211.             bmp.Canvas.Handle,
  212.             bx * BLOCK_SIZE, by * BLOCK_SIZE,
  213.             SRCCOPY);
  214.             }
  215.  
  216.  
  217.             // Comprimir bloque como JPEG
  218.             BlockMs.Clear;
  219.             BlockJpg.Assign(BlockBmp);
  220.             BlockJpg.CompressionQuality := 45;  // calidad de comprension
  221.             BlockJpg.SaveToStream(BlockMs);
  222.  
  223.             // Empaquetar: [bx 2B][by 2B][size 4B][data]
  224.             BlockSize32 := BlockMs.Size;
  225.             SetLength(BlockBytes, 8 + Integer(BlockSize32));
  226.             BlockBytes[1] := AnsiChar(bx shr 8);
  227.             BlockBytes[2] := AnsiChar(bx and $FF);
  228.             BlockBytes[3] := AnsiChar(by shr 8);
  229.             BlockBytes[4] := AnsiChar(by and $FF);
  230.             BlockBytes[5] := AnsiChar(BlockSize32 shr 24);
  231.             BlockBytes[6] := AnsiChar((BlockSize32 shr 16) and $FF);
  232.             BlockBytes[7] := AnsiChar((BlockSize32 shr  8) and $FF);
  233.             BlockBytes[8] := AnsiChar(BlockSize32 and $FF);
  234.             Move(BlockMs.Memory^, BlockBytes[9], BlockSize32);
  235.  
  236.             //FinalPacket := FinalPacket + BlockBytes;
  237.             FinalStream.Write(BlockBytes[1], Length(BlockBytes));  // ← O(1) siempre
  238.  
  239.             Inc(ChangedCount);
  240.           end;
  241.  
  242.           if ChangedCount > 0 then
  243.           begin
  244.             SetLength(BlockBytes, 5);
  245.             BlockBytes[1] := AnsiChar($02);
  246.             BlockBytes[2] := AnsiChar(ChangedCount shr 24);
  247.             BlockBytes[3] := AnsiChar((ChangedCount shr 16) and $FF);
  248.             BlockBytes[4] := AnsiChar((ChangedCount shr  8) and $FF);
  249.             BlockBytes[5] := AnsiChar(ChangedCount and $FF);
  250.             FinalPacket := BlockBytes + FinalPacket;
  251.             {
  252.             WriteInt64BigEndian(FSock, Length(FinalPacket));
  253.             Send(FSock, @FinalPacket[1], Length(FinalPacket), 0);
  254.             }
  255.               // Enviar tamaño total
  256.   WriteInt64BigEndian(FSock, 5 + FinalStream.Size);
  257.   // Enviar header
  258.   Send(FSock, @BlockBytes[1], 5, 0);
  259.   // Enviar bloques de un golpe
  260.   Send(FSock, FinalStream.Memory, FinalStream.Size, 0);
  261.           end;
  262.  
  263.  
  264.           // Actualizar buffer con frame actual
  265.           for py := 0 to bmp.Height - 1 do
  266.           begin
  267.             CurRow := bmp.ScanLine[py];
  268.             Move(CurRow^[0], PrevData[py * RowBytes], RowBytes);
  269.           end;
  270.         end;
  271.  
  272.       except
  273.         on E: Exception do
  274.         begin
  275.           frm_Main.Log('Capture ERROR: ' + E.Message);
  276.           Sleep(100);
  277.           Continue;  // ← no romper el loop, continuar
  278.         end;
  279.       end;
  280.  
  281.       // Control FPS según resolución
  282.       if BmpWidth >= 1920 then TargetMs := 100  // 10 FPS en 1920 — red no se satura
  283.       else if BmpWidth >= 1280 then TargetMs := 66  // 15 FPS
  284.       else TargetMs := 50;  // 20 FPS
  285.  
  286.       if GetTickCount64 - fpsTimer < TargetMs then
  287.         Sleep(TargetMs - Integer(GetTickCount64 - fpsTimer))
  288.       else
  289.         Sleep(5);
  290.     end;
  291.  
  292.   finally
  293.  
  294.   bmp.Free;
  295.   jpg.Free;
  296.   ms.Free;
  297.  
  298.     BlockMs.Free;
  299.     BlockJpg.Free;
  300.     BlockBmp.Free;
  301.   end;
  302.  
  303.   frm_Main.Log('Thread pantalla terminado');
  304. end;

And this is the receiver (client):

Code: Pascal  [Select][+][-]
  1. procedure TThread_Connection_Desktop.Execute;
  2. var
  3.   FrameSize: Int64;
  4.   Data: RawByteString;
  5.   TotalRead, Got: Integer;
  6. begin
  7.   frm_Main.Log('RECV Thread Desktop iniciado FSock=' + IntToStr(FSock));
  8.  
  9.   while not Terminated do
  10.   begin
  11.     if FSock = INVALID_SOCKET then Break;
  12.  
  13.     FrameSize := ReadInt64BigEndian(FSock);
  14.     if FrameSize <= 0 then Break;
  15.     if FrameSize > 50 * 1024 * 1024 then
  16.     begin
  17.       frm_Main.Log('RECV tamaño inválido: ' + IntToStr(FrameSize));
  18.       Break;
  19.     end;
  20.  
  21.     SetLength(Data, FrameSize);
  22.     TotalRead := 0;
  23.     while TotalRead < FrameSize do
  24.     begin
  25.       Got := Recv(FSock, @Data[TotalRead + 1], FrameSize - TotalRead, 0);
  26.       if Got <= 0 then begin Terminate; Break; end;
  27.       Inc(TotalRead, Got);
  28.     end;
  29.     if Terminated then Break;
  30.     if Length(Data) < 2 then Continue;
  31.  
  32.     if not frm_Main.FFramePending then
  33.     begin
  34.       frm_Main.FFramePending := True;
  35.       frm_Main.FFrameData := Data;
  36.       TThread.Queue(nil, frm_Main.SincronizarActualizarPantalla);
  37.     end;
  38.   end;
  39.  
  40.   frm_Main.Log('RECV Thread Desktop terminado');
  41. end;  
  42.  
  43. procedure Tfrm_Main.SincronizarActualizarPantalla;
  44. var
  45.   Data: RawByteString;
  46.   Header: Byte;
  47.   BlockCount, i: Integer;
  48.   bx, by: Integer;
  49.   BlockDataSize: LongWord;
  50.   Offset: Integer;
  51.   BlockMs: TMemoryStream;
  52.   BlockJpg: TJPEGImage;
  53.   BlockBmp: TBitmap;
  54.  
  55.     TmpW, TmpH: Integer;
  56.  
  57.  
  58. begin
  59.   FFramePending := False;
  60.   Data := FFrameData;
  61.   FFrameData := '';
  62.  
  63.   if Length(Data) < 2 then Exit;
  64.   if not (Assigned(frm_RemoteScreen) and frm_RemoteScreen.Visible) then Exit;
  65.  
  66.   Header := Byte(Data[1]);
  67.     Log('SYNC header=' + IntToStr(Header) + ' DataLen=' + IntToStr(Length(Data)));
  68.  
  69.  
  70.   // ── FRAME COMPLETO ($FF) ──────────────────────────────────────
  71.   if Header = $FF then
  72. begin
  73.   FPersistMs.Clear;
  74.   FPersistMs.WriteBuffer(Data[2], Length(Data) - 1);
  75.   FPersistMs.Position := 0;
  76.   try
  77.     FPersistJpg.LoadFromStream(FPersistMs);
  78.     TmpW := FPersistJpg.Width;
  79.     TmpH := FPersistJpg.Height;
  80.  
  81.     // Guardar resolución real UNA sola vez desde el frame completo
  82.     ResolutionTargetWidth  := TmpW;
  83.     ResolutionTargetHeight := TmpH;
  84.  
  85.     if (FPersistBmp.Width  <> TmpW) or
  86.        (FPersistBmp.Height <> TmpH) or
  87.        (FPersistBmp.PixelFormat <> pf24bit) then
  88.     begin
  89.       FPersistBmp.PixelFormat := pf24bit;
  90.       FPersistBmp.SetSize(TmpW, TmpH);
  91.     end;
  92.     FPersistBmp.Canvas.Draw(0, 0, FPersistJpg);
  93.   except
  94.     on E: Exception do begin Log('Error JPEG full: ' + E.Message); Exit; end;
  95.   end;
  96.   FLastPaintTime := GetTickCount64;
  97.   frm_RemoteScreen.UpdateScreenImage(FPersistBmp);
  98.   Exit;
  99. end;
  100.  
  101.   // ── PAQUETE DELTA ($02) ───────────────────────────────────────
  102.   if Header = $02 then
  103.   begin
  104.         Log('SYNC delta BlockCount previo=' + IntToStr(FPersistBmp.Width));
  105.  
  106.         if Length(Data) < 5 then begin Log('SYNC delta data corta'); Exit; end;
  107.  
  108.         if FPersistBmp.Width = 0 then begin Log('SYNC delta sin frame base'); Exit; end;
  109.  
  110.  
  111.     BlockCount := (Byte(Data[2]) shl 24) or (Byte(Data[3]) shl 16) or
  112.                   (Byte(Data[4]) shl  8) or  Byte(Data[5]);
  113.         Log('SYNC delta BlockCount=' + IntToStr(BlockCount));
  114.  
  115.  
  116.     if BlockCount <= 0 then Exit;
  117.  
  118.     BlockMs  := TMemoryStream.Create;
  119.     BlockJpg := TJPEGImage.Create;
  120.     BlockBmp := TBitmap.Create;
  121.     try
  122.       Offset := 6;  // byte 6 = primer bloque
  123.  
  124.       for i := 0 to BlockCount - 1 do
  125.       begin
  126.         if Offset + 7 > Length(Data) then Break;
  127.  
  128.         bx := (Byte(Data[Offset])   shl 8) or Byte(Data[Offset+1]);
  129.         by := (Byte(Data[Offset+2]) shl 8) or Byte(Data[Offset+3]);
  130.         BlockDataSize :=
  131.           (Byte(Data[Offset+4]) shl 24) or (Byte(Data[Offset+5]) shl 16) or
  132.           (Byte(Data[Offset+6]) shl  8) or  Byte(Data[Offset+7]);
  133.         Inc(Offset, 8);
  134.  
  135.         if Offset + Integer(BlockDataSize) - 1 > Length(Data) then Break;
  136.  
  137.         BlockMs.Clear;
  138.         BlockMs.WriteBuffer(Data[Offset], BlockDataSize);
  139.         BlockMs.Position := 0;
  140.         Inc(Offset, BlockDataSize);
  141.  
  142.         try
  143.           BlockJpg.LoadFromStream(BlockMs);
  144.           // Crear bitmap temporal con pf24bit ANTES de asignar
  145.           BlockBmp.Free;
  146.           BlockBmp := TBitmap.Create;
  147.           BlockBmp.PixelFormat := pf24bit;
  148.           BlockBmp.Assign(BlockJpg);
  149.         except
  150.           Continue;
  151.         end;
  152.  
  153.         // Pegar bloque en su posición sobre FPersistBmp
  154.         {
  155.         BitBlt(
  156.           FPersistBmp.Canvas.Handle,
  157.           bx * 64, by * 64,
  158.           BlockBmp.Width, BlockBmp.Height,
  159.           BlockBmp.Canvas.Handle,
  160.           0, 0, SRCCOPY);
  161.         }
  162.         FPersistBmp.Canvas.Draw(bx * 64, by * 64, BlockBmp);
  163.       end;
  164.  
  165.       FLastPaintTime := GetTickCount64;
  166.  
  167.       {
  168.       frm_RemoteScreen.UpdateScreenImage(FPersistBmp);
  169.       }
  170.       if not Assigned(FDisplayBmp) then
  171. begin
  172.   FDisplayBmp := TBitmap.Create;
  173.   FDisplayBmp.PixelFormat := pf24bit;
  174. end;
  175. if (FDisplayBmp.Width <> FPersistBmp.Width) or
  176.    (FDisplayBmp.Height <> FPersistBmp.Height) then
  177.   FDisplayBmp.SetSize(FPersistBmp.Width, FPersistBmp.Height);
  178.  
  179. FDisplayBmp.Canvas.Draw(0, 0, FPersistBmp);  // copia rápida
  180. frm_RemoteScreen.UpdateScreenImage(FDisplayBmp);
  181.  
  182.  
  183.  
  184.     finally
  185.       BlockBmp.Free;
  186.       BlockJpg.Free;
  187.       BlockMs.Free;
  188.     end;
  189.   end;
  190. end;

The same client works both as a PC-controller and a PC-controlled device.
Any help or suggestions would be greatly appreciated. I just need to work on this part to make the transmission smoother and the windows move faster. I promise that when I can get it working smoothly, I'll share the project with you.

I love desktop software
https://www.ericksystem.com

rvk

  • Hero Member
  • *****
  • Posts: 7018
Re: Remote desktop software in Lazarus
« Reply #9 on: April 10, 2026, 04:25:06 pm »
You can try switching to UDP but I'm not sure if it will make much difference.

But there is a very big other thing to consider... how are you processing the frames/screens? Are you just receiving them and displaying them? In that case... you need to consider that the network is buffering these screens. So... when the transmitting is not fast... all those screens are buffered and showed by you. That's NOT very efficient.

You might want to build in a check on ReadScreen for UDP or TCP. If there is MORE than 1 screen waiting... just only display the last one and discard the others.

Thank you so much for your replies, friends. I have a question: how can I use H.264 for video streaming in Lazarus Pascal? I've heard it's the fastest way to stream screens.
As I mentioned, I'm about 90% of the way through the program; the only thing I need to fix is ​​the slowness when moving windows. I was thinking of using UDP, as you mentioned it's faster than TCP.

Yes. You could use a library for screengrab (like scrap like Rustdesk does) or use gdigrab like ffmpeg can (although that last one is also considered slow).

Just for fun, here are 2 commands to send a screen-stream from server to client.

On the client (receiving end):
Code: Text  [Select][+][-]
  1. ffplay -fflags nobuffer -flags low_delay -framedrop -probesize 32 -analyzeduration 0 -sync ext udp://0.0.0.0:1234
On the server (change IP to your client):
Code: Text  [Select][+][-]
  1. ffmpeg -f gdigrab -framerate 30 -i desktop -c:v libx264 -preset ultrafast -tune zerolatency -pix_fmt yuv420p -f mpegts udp://192.168.2.11:1234

See that the client doesn't do buffering. If for example you just do a simple ffplay it defaults to buffering and you see it's a lot lot lot slower.
Code: Text  [Select][+][-]
  1. ffplay -f mpegts udp://0.0.0.0:1234

For the server, instead of the libx264 you could use one of the hardware ones (h264_qsv for intel, h264_amf for AMD and h264_nvenc for NVidea).
« Last Edit: April 10, 2026, 04:27:32 pm by rvk »

Ericktux

  • Sr. Member
  • ****
  • Posts: 394
    • ericksystem software
Re: Remote desktop software in Lazarus
« Reply #10 on: April 10, 2026, 07:35:44 pm »
Thank you so much for your response, friend. For the "ffmpeg" method, do I need to download:
ffmpeg.exe
ffplay.exe
ffprobe.exe
and use them as subprocesses, calling them with tprocess?
Or is there an ffmpeg DLL for Lazarus that would make integration easier?  :-\
I love desktop software
https://www.ericksystem.com

rvk

  • Hero Member
  • *****
  • Posts: 7018
Re: Remote desktop software in Lazarus
« Reply #11 on: April 10, 2026, 08:20:14 pm »
Thank you so much for your response, friend. For the "ffmpeg" method, do I need to download:
ffmpeg.exe
ffplay.exe
ffprobe.exe
and use them as subprocesses, calling them with tprocess?
Or is there an ffmpeg DLL for Lazarus that would make integration easier?  :-\
You can download ffmpeg and ffplay here.
https://www.ffmpeg.org/download.html#build-windows

There are dll which you can use.
The commands I gave are just for demonstrating the streaming of the desktop.

Of course if you want real remote desktop control this is not sufficient (because this just transfers and shows the desktop on the client). But it demonstrates the possible speed to see if that's sufficient and faster than the VNC CopyRect method.

For remote control you could use dll to send the screen as stream and at the same time you would need to receive instructions for control (maybe in another thread). And on the side of the client you would need to substitute ffplay for reading and sending that control.

For the client there already seems to be a ffplay wrapper for Lazarus. You might look into that and see if remote control sending back to the server could be added.
https://forum.lazarus.freepascal.org/index.php?topic=44247.0



Xenno

  • Jr. Member
  • **
  • Posts: 88
    • BS Programs
Re: Remote desktop software in Lazarus
« Reply #12 on: April 11, 2026, 09:41:55 am »
The resulting bitmap is already good enough to be sent with more bytes consequency. To reduce size it is possible to just compress the bitmap stream with any compression library and let the receiver uncompress to original bitmap; sharing the hard work. If really needed to convert to JPG, very low compression level is prefered to avoid overhead. JPG does not allow zero compression. It needs a wise decision to balance between CPU usage and network package size.

AFAIK, there are options for screen capture or record in Windows such as DDraw, GDI, and DXGI. DXGI is available in recent Windows. For performance reason it is said choose DXGI but I have not found an implementation to make it work in Lazarus, yet. I tried to use DXGI for screen recording but failed. Too hard for me. That time, since my app targets to produce MP4, I decided to switch to ffmpeg.exe with gdigrab. Mind the license.

Anyway, I often use a simple PrintWindow function (runs in a thread) for screen capture.

A project below worth a look:
https://github.com/spawn451/Remote-Desktop-Streamer-Delphi
Lazarus 4.0, Windows 10, https://www.youtube.com/@bsprograms

domasz

  • Hero Member
  • *****
  • Posts: 629
Re: Remote desktop software in Lazarus
« Reply #13 on: April 11, 2026, 12:02:48 pm »
This method often gives good results:
1) compare current screen shot with previous one
2) pixels which are the same in the current shot replace with black pixels
3) save the result as .jpeg
4) create a new bitmap. compare current and previous shots again
5) this time same pixels make black and different pixels make white
6) this gives you an alpha channel. Compress the result to PNG, GIF or just GZIP
7) send both images to the client. reconstruct new frame.
8) bonus- if the difference between current and new shot is too big- just send the new shot in jpeg.

Ericktux

  • Sr. Member
  • ****
  • Posts: 394
    • ericksystem software
Re: Remote desktop software in Lazarus
« Reply #14 on: April 11, 2026, 02:22:55 pm »
Good morning, friends. I'm sharing my tests.
I changed the component that displays the remote desktop from

Screen_Image:Timage
to
Screen_Image:TBGLVirtualScreen.

I did this to enable hardware acceleration, but in my tests, it's still slow when moving windows the same initial problem.
This is the code I have in the "form_remotescreen" unit with TBGLVirtualScreen and "hardware acceleration", which is responsible for displaying the shared screen. It's located on the client side.

Code: Pascal  [Select][+][-]
  1. unit Form_RemoteScreen;
  2. {$MODE Delphi}
  3. interface
  4. uses
  5.   Windows,
  6.   Messages,
  7.   SysUtils,
  8.   Classes,
  9.  
  10.   Sockets, synautil, blcksock,
  11.   JwaTlHelp32,
  12.   GL,
  13.  
  14.   //LResources,
  15.  
  16.   Graphics,
  17.   Controls,
  18.   Forms,
  19.   Dialogs,
  20.   ExtCtrls,
  21.   StdCtrls, Buttons, ComCtrls,
  22.   ZLib,
  23.   BGRABitmap, BGRABitmapTypes,   // ya lo tienes en Form_Main, pero agrégalo aquí también por seguridad
  24.   BGLVirtualScreen,
  25.   BGRAOpenGL;
  26.  
  27.   //IdGlobal;
  28.  
  29. type
  30.   PKBDLLHOOKSTRUCT = ^TKBDLLHOOKSTRUCT;
  31.   TKBDLLHOOKSTRUCT = record
  32.     vkCode: DWORD;
  33.     scanCode: DWORD;
  34.     flags: DWORD;
  35.     time: DWORD;
  36.     dwExtraInfo: ULONG_PTR;
  37.   end;
  38.  
  39.  
  40. type
  41.  
  42.   { Tfrm_RemoteScreen }
  43.  
  44.   Tfrm_RemoteScreen = class(TForm)
  45.     BitBtn1: TBitBtn;
  46.     BitBtn2: TBitBtn;
  47.     Chat_Image: TImage;
  48.     FileShared_Image: TImage;
  49.     KeyboardRemote_CheckBox: TCheckBox;
  50.     Quality_Label: TLabel;
  51.     MouseRemote_CheckBox: TCheckBox;
  52.     Panel_controles: TPanel;
  53.     Resize_CheckBox: TCheckBox;
  54.     ScreenStart_Image: TImage;
  55.     CaptureKeys_Timer: TTimer;
  56.     Quality_TrackBar: TTrackBar;
  57.     Screen_Image: TBGLVirtualScreen;
  58.     ScrollBox1: TScrollBox;
  59.     procedure BitBtn1Click(Sender: TObject);
  60.     procedure BitBtn2Click(Sender: TObject);
  61.     procedure FormCreate(Sender: TObject);
  62.     procedure FormShow(Sender: TObject);
  63.     procedure FormClose(Sender: TObject; var Action: TCloseAction);
  64.     procedure FormMouseWheel(Sender: TObject; Shift: TShiftState; WheelDelta: Integer; MousePos: TPoint; var Handled: Boolean);
  65.     procedure KeyboardRemote_CheckBoxChange(Sender: TObject);
  66.     procedure MouseRemote_CheckBoxChange(Sender: TObject);
  67.     procedure MouseRemote_CheckBoxClick(Sender: TObject);
  68.     procedure KeyboardRemote_CheckBoxClick(Sender: TObject);
  69.     procedure Resize_CheckBoxChange(Sender: TObject);
  70.     procedure Resize_CheckBoxClick(Sender: TObject);
  71.     procedure MouseRemote_CheckBoxKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
  72.     procedure KeyboardRemote_CheckBoxKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
  73.     procedure Resize_CheckBoxKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
  74.     procedure Screen_ImageMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
  75.     procedure Screen_ImageMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
  76.     procedure Screen_ImageMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
  77.     procedure Chat_ImageClick(Sender: TObject);
  78.     procedure FileShared_ImageClick(Sender: TObject);
  79.     procedure CaptureKeys_TimerTimer(Sender: TObject);
  80.     procedure Screen_ImagePaint(Sender: TObject);
  81.     procedure Quality_TrackBarChange(Sender: TObject);
  82.     procedure Screen_ImageRedraw(Sender: TObject; BGLContext: TBGLContext);
  83.   private
  84.     //FLastBitmap: TBitmap;
  85.     FLastBitmap: TBitmap;           // puedes mantenerlo si quieres, pero ya no es necesario para dibujar
  86.     RemoteTexture: IBGLTexture;     // ← NUEVA: textura acelerada por GPU
  87.     FKeyState: array[0..255] of Boolean;
  88.     FKeyboardHook: HHOOK;
  89.  
  90.     procedure WMGetMinMaxInfo(var Message: TWMGetMinMaxInfo); message WM_GETMINMAXINFO;
  91.  
  92.   public
  93.     CtrlPressed, ShiftPressed, AltPressed: Boolean;
  94.     procedure UpdateScreenImage(Bitmap: TBitmap);  // para sincronizar desde thread
  95.   end;
  96.  
  97. var
  98.   frm_RemoteScreen: Tfrm_RemoteScreen;
  99.     GlobalKeyboardSock: TSocket = INVALID_SOCKET;
  100.  
  101.  
  102. const
  103.   WH_KEYBOARD_LL = 13;
  104.  
  105. implementation
  106. {$R *.lfm}
  107.  
  108.  
  109. uses
  110.   Form_Main,
  111.   Form_Chat,
  112.   Form_ShareFiles,
  113.  
  114.  
  115. SynSock,
  116. WinSock;
  117.  
  118.  
  119. function LowLevelKeyboardProc(nCode: Integer; wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall;
  120. var
  121.   pKHS: PKBDLLHOOKSTRUCT;
  122.   vkCode: DWORD;
  123.   S: AnsiString;
  124. begin
  125.   Result := 0;
  126.   if nCode < 0 then
  127.   begin
  128.     Result := CallNextHookEx(0, nCode, wParam, lParam);
  129.     Exit;
  130.   end;
  131.  
  132.   pKHS := PKBDLLHOOKSTRUCT(lParam);
  133.   vkCode := pKHS^.vkCode;
  134.  
  135.   if vkCode in [VK_LWIN, VK_RWIN] then
  136.   begin
  137.     if GlobalKeyboardSock <> INVALID_SOCKET then
  138.     begin
  139.       if (wParam = WM_KEYDOWN) or (wParam = WM_SYSKEYDOWN) then
  140.         S := AnsiString('<|REDIRECT|><|KEYDOWN|>' + IntToStr(vkCode) + '<|END|>')
  141.       else
  142.         S := AnsiString('<|REDIRECT|><|KEYUP|>' + IntToStr(vkCode) + '<|END|>');
  143.       Send(GlobalKeyboardSock, @S[1], Length(S), 0);
  144.     end;
  145.     Result := 1;
  146.     Exit;
  147.   end;
  148.  
  149.   Result := CallNextHookEx(0, nCode, wParam, lParam);
  150. end;
  151.  
  152. procedure SendStrRaw(ASock: TSocket; const Cmd: string);
  153. var
  154.   S: AnsiString;
  155. begin
  156.   if ASock = INVALID_SOCKET then Exit;
  157.   S := AnsiString(Cmd);
  158.   Send(ASock, @S[1], Length(S), 0);
  159. end;
  160.  
  161. procedure Tfrm_RemoteScreen.WMGetMinMaxInfo(var Message: TWMGetMinMaxInfo);
  162. var
  163.   MinMaxInfo: PMinMaxInfo;
  164. begin
  165.   inherited;
  166.   MinMaxInfo := Message.MinMaxInfo;
  167.   MinMaxInfo^.ptMinTrackSize.X := 800;
  168.   MinMaxInfo^.ptMinTrackSize.Y := 500;
  169.   if Resize_CheckBox.Checked then
  170.   begin
  171.     MinMaxInfo^.ptMaxTrackSize.X := frm_Main.ResolutionTargetWidth;
  172.     MinMaxInfo^.ptMaxTrackSize.Y := frm_Main.ResolutionTargetHeight;
  173.   end
  174.   else
  175.   begin
  176.     MinMaxInfo^.ptMaxTrackSize.X := frm_Main.ResolutionTargetWidth + 25;
  177.     MinMaxInfo^.ptMaxTrackSize.Y := frm_Main.ResolutionTargetHeight + 130;
  178.   end;
  179. end;
  180.  
  181.  
  182.  
  183. procedure Tfrm_RemoteScreen.FormCreate(Sender: TObject);
  184. var
  185.   Cur: TCursorImage;
  186. begin
  187.   SetWindowLong(Handle, GWL_EXSTYLE, WS_EX_APPWINDOW);
  188.   CtrlPressed  := False;
  189.   ShiftPressed := False;
  190.   AltPressed   := False;
  191.   DoubleBuffered := True;
  192.  
  193.   // ← AGREGAR: permitir que Screen_Image pase eventos al form
  194.   Screen_Image.OnMouseDown := Screen_ImageMouseDown;
  195.   Screen_Image.OnMouseMove := Screen_ImageMouseMove;
  196.   Screen_Image.OnMouseUp   := Screen_ImageMouseUp;
  197.  
  198.   // ← AGREGAR: el ScrollBox también debe pasar el wheel
  199.   ScrollBox1.OnMouseWheel := FormMouseWheel;
  200. end;
  201.  
  202. procedure Tfrm_RemoteScreen.BitBtn1Click(Sender: TObject);
  203. begin
  204.   frm_ShareFiles.Show;
  205. end;
  206.  
  207. procedure Tfrm_RemoteScreen.BitBtn2Click(Sender: TObject);
  208. var
  209.   Renderer, Vendor, Version: string;
  210. begin
  211.   // ... tu código existente ...
  212.  
  213.   // Verificar OpenGL
  214.   Renderer := string(glGetString(GL_RENDERER));
  215.   Vendor   := string(glGetString(GL_VENDOR));
  216.   Version  := string(glGetString(GL_VERSION));
  217.  
  218.   frm_Main.Log('GPU Renderer : ' + Renderer);
  219.   frm_Main.Log('GPU Vendor   : ' + Vendor);
  220.   frm_Main.Log('OpenGL Ver   : ' + Version);
  221.  
  222.   // Si Renderer contiene "GDI" o "Software" = NO hay aceleración
  223.   if (Pos('GDI', UpperCase(Renderer)) > 0) or
  224.      (Pos('SOFTWARE', UpperCase(Renderer)) > 0) or
  225.      (Pos('LLVMPIPE', UpperCase(Renderer)) > 0) then
  226.      ShowMessage('⚠ SIN aceleración de hardware (software renderer)')
  227.     //frm_Main.Log('⚠ SIN aceleración de hardware (software renderer)')
  228.   else
  229.   ShowMessage('✓ Aceleración de hardware activa: ' + Renderer);
  230.    // frm_Main.Log('✓ Aceleración de hardware activa: ' + Renderer);
  231. end;
  232.  
  233. procedure Tfrm_RemoteScreen.FormShow(Sender: TObject);
  234. begin
  235.   FillChar(FKeyState, SizeOf(FKeyState), 0);
  236.   CtrlPressed  := False;
  237.   ShiftPressed := False;
  238.   AltPressed   := False;
  239.   Resize_CheckBox.Checked := False;
  240.   Resize_CheckBoxClick(nil);
  241.  
  242.   // ← AGREGAR: forzar foco al form para que GetAsyncKeyState funcione
  243.   Application.ProcessMessages;
  244.   SetFocus;
  245. end;
  246.  
  247. procedure Tfrm_RemoteScreen.FormClose(Sender: TObject; var Action: TCloseAction);
  248. begin
  249.   if Assigned(RemoteTexture) then FreeAndNil(RemoteTexture);
  250.   CaptureKeys_Timer.Enabled := False;
  251.   if FKeyboardHook <> 0 then
  252.   begin
  253.     UnhookWindowsHookEx(FKeyboardHook);
  254.     FKeyboardHook := 0;
  255.   end;
  256.   GlobalKeyboardSock := INVALID_SOCKET;
  257.   if frm_Main.MainSock <> INVALID_SOCKET then
  258.     frm_Main.SendMain('<|STOPACCESS|><|END|>');
  259.   frm_Main.SetOnline;
  260.   frm_Main.Show;
  261. end;
  262.  
  263. procedure Tfrm_RemoteScreen.UpdateScreenImage(Bitmap: TBitmap);
  264. var
  265.   TempBGRA: TBGRABitmap;
  266. begin
  267.   if not Assigned(Bitmap) or (Bitmap.Width = 0) or (Bitmap.Height = 0) then Exit;
  268.  
  269.   // Actualizar resolución target (mantienes tu lógica)
  270.   frm_Main.ResolutionTargetWidth  := Bitmap.Width;
  271.   frm_Main.ResolutionTargetHeight := Bitmap.Height;
  272.  
  273.   // Crear/copiar a TBGRABitmap y subir a GPU como textura
  274.   TempBGRA := TBGRABitmap.Create(Bitmap);
  275.   try
  276.     if Assigned(RemoteTexture) then
  277.       FreeAndNil(RemoteTexture);           // libera la textura anterior
  278.  
  279.     RemoteTexture := BGLTexture(TempBGRA); // ← Sube la imagen a la GPU (aceleración por hardware)
  280.  
  281.     Screen_Image.Invalidate;          // fuerza redraw con OpenGL
  282.   finally
  283.     TempBGRA.Free;
  284.   end;
  285. end;
  286.  
  287. // ==================== MOUSE ====================
  288. procedure Tfrm_RemoteScreen.Screen_ImageMouseMove(Sender: TObject;
  289.   Shift: TShiftState; X, Y: Integer);
  290. var
  291.   RealX, RealY: Integer;
  292.   ExtraFlag: DWORD;
  293.   ImgW, ImgH: Integer;
  294. begin
  295.   if not MouseRemote_CheckBox.Checked or
  296.      (frm_Main.KeyboardSock = INVALID_SOCKET) then Exit;
  297.  
  298.   // Usar dimensiones reales del Image para el cálculo
  299.   ImgW := Screen_Image.Width;
  300.   ImgH := Screen_Image.Height;
  301.   if (ImgW = 0) or (ImgH = 0) then Exit;
  302.  
  303.   RealX := MulDiv(X, frm_Main.ResolutionTargetWidth,  ImgW);
  304.   RealY := MulDiv(Y, frm_Main.ResolutionTargetHeight, ImgH);
  305.  
  306.   // Clampear para no salir del rango
  307.   if RealX < 0 then RealX := 0;
  308.   if RealY < 0 then RealY := 0;
  309.   if RealX >= frm_Main.ResolutionTargetWidth  then RealX := frm_Main.ResolutionTargetWidth  - 1;
  310.   if RealY >= frm_Main.ResolutionTargetHeight then RealY := frm_Main.ResolutionTargetHeight - 1;
  311.  
  312.   ExtraFlag := 0;
  313.   if ssLeft   in Shift then ExtraFlag := MOUSEEVENTF_LEFTDOWN;
  314.   if ssRight  in Shift then ExtraFlag := MOUSEEVENTF_RIGHTDOWN;
  315.   if ssMiddle in Shift then ExtraFlag := MOUSEEVENTF_MIDDLEDOWN;
  316.  
  317.   SendStrRaw(frm_Main.KeyboardSock,
  318.     '<|REDIRECT|><|SETMOUSEPOS|>' + IntToStr(RealX) + '<|>' +
  319.     IntToStr(RealY) + '<|>' + IntToStr(ExtraFlag) + '<|END|>');
  320. end;
  321.  
  322. procedure Tfrm_RemoteScreen.Screen_ImageMouseDown(Sender: TObject;
  323.   Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
  324. var
  325.   RealX, RealY: Integer;
  326.   ImgW, ImgH: Integer;
  327. begin
  328.   //SetFocus;
  329.  
  330.   if not MouseRemote_CheckBox.Checked or
  331.      (frm_Main.KeyboardSock = INVALID_SOCKET) then Exit;
  332.  
  333.   ImgW := Screen_Image.Width;
  334.   ImgH := Screen_Image.Height;
  335.   if (ImgW = 0) or (ImgH = 0) then Exit;
  336.  
  337.   RealX := MulDiv(X, frm_Main.ResolutionTargetWidth,  ImgW);
  338.   RealY := MulDiv(Y, frm_Main.ResolutionTargetHeight, ImgH);
  339.  
  340.   if RealX < 0 then RealX := 0;
  341.   if RealY < 0 then RealY := 0;
  342.   if RealX >= frm_Main.ResolutionTargetWidth  then RealX := frm_Main.ResolutionTargetWidth  - 1;
  343.   if RealY >= frm_Main.ResolutionTargetHeight then RealY := frm_Main.ResolutionTargetHeight - 1;
  344.  
  345.   case Button of
  346.     mbLeft:   SendStrRaw(frm_Main.KeyboardSock,
  347.                 '<|REDIRECT|><|SETMOUSELEFTCLICKDOWN|>' +
  348.                 IntToStr(RealX) + '<|>' + IntToStr(RealY) + '<|END|>');
  349.     mbRight:  SendStrRaw(frm_Main.KeyboardSock,
  350.                 '<|REDIRECT|><|SETMOUSERIGHTCLICKDOWN|>' +
  351.                 IntToStr(RealX) + '<|>' + IntToStr(RealY) + '<|END|>');
  352.     mbMiddle: SendStrRaw(frm_Main.KeyboardSock,
  353.                 '<|REDIRECT|><|SETMOUSEMIDDLEDOWN|>' +
  354.                 IntToStr(RealX) + '<|>' + IntToStr(RealY) + '<|END|>');
  355.   end;
  356. end;
  357.  
  358. procedure Tfrm_RemoteScreen.Screen_ImageMouseUp(Sender: TObject;
  359.   Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
  360. var
  361.   RealX, RealY: Integer;
  362. begin
  363.   if not MouseRemote_CheckBox.Checked or
  364.      (frm_Main.KeyboardSock = INVALID_SOCKET) then Exit;
  365.  
  366.   RealX := MulDiv(X, frm_Main.ResolutionTargetWidth,  Screen_Image.Width);
  367.   RealY := MulDiv(Y, frm_Main.ResolutionTargetHeight, Screen_Image.Height);
  368.  
  369.   case Button of
  370.     mbLeft:   SendStrRaw(frm_Main.KeyboardSock,
  371.                 '<|REDIRECT|><|SETMOUSELEFTCLICKUP|>' +
  372.                 IntToStr(RealX) + '<|>' + IntToStr(RealY) + '<|END|>');
  373.     mbRight:  SendStrRaw(frm_Main.KeyboardSock,
  374.                 '<|REDIRECT|><|SETMOUSERIGHTCLICKUP|>' +
  375.                 IntToStr(RealX) + '<|>' + IntToStr(RealY) + '<|END|>');
  376.     mbMiddle: SendStrRaw(frm_Main.KeyboardSock,
  377.                 '<|REDIRECT|><|SETMOUSEMIDDLEUP|>' +
  378.                 IntToStr(RealX) + '<|>' + IntToStr(RealY) + '<|END|>');
  379.   end;
  380. end;
  381.  
  382. procedure Tfrm_RemoteScreen.FormMouseWheel(Sender: TObject; Shift: TShiftState; WheelDelta: Integer; MousePos: TPoint; var Handled: Boolean);
  383. begin
  384.   if MouseRemote_CheckBox.Checked and (frm_Main.KeyboardSock <> INVALID_SOCKET) then
  385.     SendStrRaw(frm_Main.KeyboardSock, '<|REDIRECT|><|WHEELMOUSE|>' + IntToStr(WheelDelta) + '<|END|>');
  386.   Handled := True;
  387. end;
  388.  
  389. procedure Tfrm_RemoteScreen.KeyboardRemote_CheckBoxChange(Sender: TObject);
  390. begin
  391.  
  392. end;
  393.  
  394. procedure Tfrm_RemoteScreen.MouseRemote_CheckBoxChange(Sender: TObject);
  395. begin
  396.  
  397. end;
  398.  
  399. // ==================== KEYBOARD ====================
  400. procedure Tfrm_RemoteScreen.CaptureKeys_TimerTimer(Sender: TObject);
  401. var
  402.   i: Byte;
  403.   IsDown: Boolean;
  404. begin
  405.   if not Active or (frm_Main.KeyboardSock = INVALID_SOCKET) then Exit;
  406.  
  407.   for i := 8 to 254 do
  408.   begin
  409.     IsDown := GetAsyncKeyState(i) and $8000 <> 0;
  410.  
  411.     if IsDown and not FKeyState[i] then
  412.     begin
  413.       FKeyState[i] := True;
  414.       SendStrRaw(frm_Main.KeyboardSock,
  415.         '<|REDIRECT|><|KEYDOWN|>' + IntToStr(i) + '<|END|>');
  416.     end
  417.     else if not IsDown and FKeyState[i] then
  418.     begin
  419.       FKeyState[i] := False;
  420.       SendStrRaw(frm_Main.KeyboardSock,
  421.         '<|REDIRECT|><|KEYUP|>' + IntToStr(i) + '<|END|>');
  422.     end;
  423.   end;
  424.  
  425.   // Actualizar estados de modificadores para compatibilidad
  426.   CtrlPressed  := FKeyState[VK_CONTROL];
  427.   ShiftPressed := FKeyState[VK_SHIFT];
  428.   AltPressed   := FKeyState[VK_MENU];
  429. end;
  430.  
  431. procedure Tfrm_RemoteScreen.Screen_ImagePaint(Sender: TObject);
  432. begin
  433.   {if not Assigned(FLastBitmap) then
  434.   begin
  435.     frm_Main.Log('PAINT FLastBitmap nil');
  436.     Exit;
  437.   end;
  438.   frm_Main.Log('PAINT ' + IntToStr(FLastBitmap.Width) + 'x' +
  439.     IntToStr(FLastBitmap.Height));
  440.   SetStretchBltMode(Screen_Image.Canvas.Handle, HALFTONE);
  441.   SetBrushOrgEx(Screen_Image.Canvas.Handle, 0, 0, nil);
  442.   StretchBlt(
  443.     Screen_Image.Canvas.Handle,
  444.     0, 0, Screen_Image.Width, Screen_Image.Height,
  445.     FLastBitmap.Canvas.Handle,
  446.     0, 0, FLastBitmap.Width, FLastBitmap.Height,
  447.     SRCCOPY
  448.   ); }
  449. end;
  450.  
  451. procedure Tfrm_RemoteScreen.Quality_TrackBarChange(Sender: TObject);
  452. begin
  453.   GJpegQuality := Quality_TrackBar.Position;
  454.   // opcional: mostrar el valor actual
  455.   Quality_Label.Caption := 'Calidad: ' + IntToStr(Quality_TrackBar.Position);
  456. end;
  457.  
  458. // =============================================
  459. // REDRAW CON ACELERACIÓN POR HARDWARE (GPU)
  460. // =============================================
  461. procedure Tfrm_RemoteScreen.Screen_ImageRedraw(Sender: TObject; BGLContext: TBGLContext);
  462. begin
  463.   BGLContext.Canvas.FillRect(0, 0, BGLContext.Width, BGLContext.Height, BGRA(0, 0, 0, 255));
  464.  
  465.   if not Assigned(RemoteTexture) then
  466.     Exit;
  467.  
  468.   // Estirar la textura al tamaño actual del control (modo stretched o 1:1)
  469.   RemoteTexture.StretchDraw(0, 0, BGLContext.Width, BGLContext.Height);
  470. end;
  471.  
  472. procedure Tfrm_RemoteScreen.KeyboardRemote_CheckBoxClick(Sender: TObject);
  473. begin
  474.   CaptureKeys_Timer.Enabled := KeyboardRemote_CheckBox.Checked;
  475.  
  476.   if KeyboardRemote_CheckBox.Checked then
  477.   begin
  478.     GlobalKeyboardSock := frm_Main.KeyboardSock;
  479.     FKeyboardHook := SetWindowsHookEx(WH_KEYBOARD_LL,
  480.       @LowLevelKeyboardProc, HInstance, 0);
  481.     frm_Main.Log('Control remoto de teclado ACTIVADO');
  482.   end
  483.   else
  484.   begin
  485.     if FKeyboardHook <> 0 then
  486.     begin
  487.       UnhookWindowsHookEx(FKeyboardHook);
  488.       FKeyboardHook := 0;
  489.     end;
  490.     GlobalKeyboardSock := INVALID_SOCKET;
  491.     frm_Main.Log('Control remoto de teclado DESACTIVADO');
  492.   end;
  493. end;
  494.  
  495. procedure Tfrm_RemoteScreen.Resize_CheckBoxChange(Sender: TObject);
  496. begin
  497.  
  498. end;
  499.  
  500. // ==================== RESIZE / STRETCH ====================
  501. procedure Tfrm_RemoteScreen.Resize_CheckBoxClick(Sender: TObject);
  502. begin
  503.   if Resize_CheckBox.Checked then
  504.   begin
  505.     // Modo Estirado (Full Window)
  506.     Screen_Image.Align := alClient;
  507.     ScrollBox1.VertScrollBar.Visible := False;
  508.     ScrollBox1.HorzScrollBar.Visible := False;
  509.   end
  510.   else
  511.   begin
  512.     // Modo Tamaño Real (1:1)
  513.     Screen_Image.Align := alNone;
  514.     Screen_Image.Width  := frm_Main.ResolutionTargetWidth;
  515.     Screen_Image.Height := frm_Main.ResolutionTargetHeight;
  516.  
  517.     ScrollBox1.VertScrollBar.Visible := True;
  518.     ScrollBox1.HorzScrollBar.Visible := True;
  519.   end;
  520.  
  521.   // Forzar redibujado inmediato
  522.   Screen_Image.Invalidate;
  523. end;
  524.  
  525. procedure Tfrm_RemoteScreen.MouseRemote_CheckBoxClick(Sender: TObject);
  526. begin
  527.   // Puedes cambiar cursor o icono si quieres
  528.   if MouseRemote_CheckBox.Checked then
  529.   begin
  530.     Screen_Image.Cursor := crCross;        // cursor de mira (opcional)
  531.     frm_Main.Log('Control remoto de mouse ACTIVADO');
  532.   end
  533.   else
  534.   begin
  535.     Screen_Image.Cursor := crDefault;
  536.     frm_Main.Log('Control remoto de mouse DESACTIVADO');
  537.   end;
  538. end;
  539.  
  540. procedure Tfrm_RemoteScreen.Chat_ImageClick(Sender: TObject);
  541. begin
  542.   frm_Chat.Show;
  543. end;
  544.  
  545. procedure Tfrm_RemoteScreen.FileShared_ImageClick(Sender: TObject);
  546. begin
  547.   frm_ShareFiles.Show;
  548. end;
  549.  
  550. procedure Tfrm_RemoteScreen.MouseRemote_CheckBoxKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
  551. begin
  552.   if Key = VK_SPACE then Key := 0;
  553. end;
  554.  
  555. procedure Tfrm_RemoteScreen.KeyboardRemote_CheckBoxKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
  556. begin
  557.   if Key = VK_SPACE then Key := 0;
  558. end;
  559.  
  560. procedure Tfrm_RemoteScreen.Resize_CheckBoxKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
  561. begin
  562.   if Key = VK_SPACE then Key := 0;
  563. end;
  564. end.

I think I'll go back to "Screen_Image:Timage" because I don't see any improvement with "Screen_Image:TBGLVirtualScreen"

I'm currently doing everything in TCP. Now I'm going to try the following:
MAIN channel (commands) → TCP
DESKTOP channel (JPG) → UDP
KEYBOARD channel (input) → UDP
FILES channel (files) → TCP

I'll run the tests to see if it's faster this way and let you know, friends.
I love desktop software
https://www.ericksystem.com

 

TinyPortal © 2005-2018