[Solved] Get notified when monitors count/resolution changed lazarus / 32-Linux ---------------------------------------------------------------------- artem101 · 2023-03-11 11:09 I need to recieve notification every time when screen resolution changed, additional monitor connected/disconnected or any other screen configuration changes. I implement this using X11 and its xrandr extension. This class simply catches any specified events in loop. Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i unit XRandREventWatcher; {$mode ObjFPC}{$H+} interface uses  Classes, SysUtils, x, xlib, xrandr, ctypes; type  TNotifyProc = procedure(const AEvent: TXEvent) of object;   { TXRandREventWatcherThread }   TXRandREventWatcherThread = class(TThread)  private    FNotifier: TNotifyProc;    FEventMask: cint;    FLastEvent: TXEvent;    procedure Notify;  protected    procedure Execute; override;  public    Constructor Create({ACreateSuspended: boolean;} AEventMask: cint;      ANotifier: TNotifyProc);  end; implementation { TXRandREventWatcherThread } procedure TXRandREventWatcherThread.Notify;begin  if Assigned(FNotifier) then  FNotifier(FLastEvent);end; procedure TXRandREventWatcherThread.Execute;var  //DisplayName: String;  Display: PDisplay;  RootWnd: TWindow;begin  //DisplayName := GetEnvironmentVariable('DISPLAY');  Display := XOpenDisplay({PChar(DisplayName)} Nil);  RootWnd := RootWindow(Display, DefaultScreen(Display));   XRRSelectInput(Display, RootWnd, FEventMask);   while True do  begin    if Terminated then    Break;     XNextEvent(Display, @FLastEvent);     Synchronize(@Notify);     // FixMe: Freezes when no events and impossible to terminate  end;   XCloseDisplay(Display);end; constructor TXRandREventWatcherThread.Create(AEventMask: cint;  ANotifier: TNotifyProc);begin  FNotifier := ANotifier;  FEventMask := AEventMask;  FreeOnTerminate := {True} False;   inherited Create(False);end; end. Usage in main form: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i unit Unit1; {$mode objfpc}{$H+} interface uses  Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls,  xlib, xrandr, XRandREventWatcher; type   { TForm1 }   TForm1 = class(TForm)    Memo1: TMemo;    procedure FormCreate(Sender: TObject);    procedure FormDestroy(Sender: TObject);  private    W: TXRandREventWatcherThread;     procedure OnEvent(const AEvent: TXEvent);    procedure UpdateScreenInfo;  public   end; var  Form1: TForm1; implementation {$R *.lfm} { TForm1 } procedure TForm1.FormCreate(Sender: TObject);begin  UpdateScreenInfo;   W := TXRandREventWatcherThread.Create(    RRScreenChangeNotifyMask, // Filter only screen configuration changed events    @onevent  );end;  procedure TForm1.FormDestroy(Sender: TObject);begin  w.Terminate;  w.Free;end; procedure TForm1.OnEvent(const AEvent: TXEvent); begin  //Memo1.Append(Format('[%s] event #%d', [timetostr(now), AEvent._type]));  UpdateScreenInfo;end; procedure TForm1.UpdateScreenInfo;var  I: Integer;begin  Screen.Upda]"]>Blockednitors;   Memo1.Clear;  Memo1.Append('Screen configuration changed on ' + TimeToStr(Now));  Memo1.Append('Monitors count: ' + IntToStr(Screen.MonitorCount));  for I := 0 to Screen.MonitorCount - 1 do  begin    Memo1.Append(Format('Monitor #%d %dx%d primary=%s', [        Screen.Monitors[I].MonitorNum,        Screen.Monitors[I].Width,        Screen.Monitors[I].Height,        BoolToStr(Screen.Monitors[I].Primary, 'Yes', 'No')    ]));  end;end; end. It works, but form freezes when I close it. Seems that loop in TXRandREventWatcherThread.Execute waits untill next event be recieved and only then will go to exit. How to terminate it? ---------------------------------------------------------------------- mikerabat · 2023-03-27 08:58 First I'm from the win world so if I'm completely wrong don't jduge Here is what I would try: The spec says that the XNextEvent actually blocks until an event arrives so either create a SigTerminatefunction like this (pseudocode!): procedure TXY.SigTerminate; begin         Terminate;         XSendEvent( ); end; and in the thread change the logic a bit such that you check the terminated flag after XNextEvent In the main window signal the termination in the formDestroy and wait for the thread to terminate. Another option is to not use the XNextEvent but rather check if there is an event in the first place - maybe XIfEvent is an option?? ---------------------------------------------------------------------- artem101 · 2023-03-28 06:20 Quote from: mikerabat on March 27, 2023, 10:58:12 am        XSendEvent( ); I don't understand well X11 system. What event I should send? Maybe there is any event which does nothing and can be catched with any event mask? ---------------------------------------------------------------------- artem101 · 2023-03-28 07:49 I add sending custom message, but still doesn't work. Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i unit XRandREventWatcher; {$mode ObjFPC}{$H+} interface uses  Classes, SysUtils, x, xlib, xrandr, ctypes; type  TNotifyProc = procedure(const AEvent: TXEvent) of object;   { TXRandREventWatcherThread }   TXRandREventWatcherThread = class(TThread)  private    FNotifier: TNotifyProc;    FEventMask: cint;    FLastEvent: TXEvent;    TerminateNotificationAtom: TAtom;    Display: PDisplay;    procedure Notify;  protected    procedure Execute; override;  public    Constructor Create({ACreateSuspended: boolean;} AEventMask: cint;      ANotifier: TNotifyProc);    destructor Destroy; override;     procedure Terminate; //override;  end; implementation uses LazLoggerBase; { TXRandREventWatcherThread } procedure TXRandREventWatcherThread.Notify;begin  if Assigned(FNotifier) then  FNotifier(FLastEvent);end; procedure TXRandREventWatcherThread.Execute;var  //DisplayName: String;  //Display: PDisplay;  RootWnd: TWindow;begin  writeln(ClassName, '::Execute() started');   //DisplayName := GetEnvironmentVariable('DISPLAY');  //Display := XOpenDisplay({PChar(DisplayName)} Nil);  RootWnd := RootWindow(Display, DefaultScreen(Display));   XRRSelectInput(Display, RootWnd, FEventMask {NoEventMask});   while True do  begin    XNextEvent(Display, @FLastEvent);     DebugLn('Recieved event: %d', [FLastEvent._type]);     if Terminated then      Break;     {if (FLastEvent._type = ClientMessage) and       (FLastEvent.message_type = TerminateNotificationAtom) then      Break;}     Synchronize(@Notify);     // FixMe: Freezes when no events and impossible to terminate  end;   //XCloseDisplay(Display);   writeln(ClassName, '::Execute() ended');end; procedure TXRandREventWatcherThread.Terminate;var  XEvent: TXClientMessageEvent;begin  DebugLn(ClassName, '::Terminate() started');   fillchar(XEvent,sizeof(XEvent),0);   XEvent._type:=clientmessage;  //ev.send_event:=???;  //ev.serial:=???;  XEvent.display:={XOpenDisplay(Nil)}Display;  XEvent.window:=RootWindow(XEvent.display, DefaultScreen(XEvent.display));  XEvent.message_type:=TerminateNotificationAtom;  XEvent.format:=32;    XSendEvent(XEvent.display, XEvent.window, False, NoEventMask, @XEvent);   inherited;   DebugLn(ClassName, '::Terminate() ended');end; constructor TXRandREventWatcherThread.Create(AEventMask: cint;  ANotifier: TNotifyProc);begin  FNotifier := ANotifier;  FEventMask := AEventMask;  FreeOnTerminate := {True} False;   Display := XOpenDisplay({PChar(DisplayName)} Nil);   TerminateNotificationAtom := XInternAtom(Display, 'XRandREventWatcher_Terminate_Notification', False);  DebugLn('TerminateNotificationAtom: %d', [TerminateNotificationAtom]);   inherited Create(False);   NameThreadForDebugging(ClassName, ThreadID);  DebugLn('Thread ', ClassName ,' started');end; destructor TXRandREventWatcherThread.Destroy;begin  DebugLn(ClassName, '::Destroy() started');   //Terminate;   inherited Destroy;   XCloseDisplay(Display);   DebugLn(ClassName, '::Destroy() ended');end; end. ---------------------------------------------------------------------- mikerabat · 2023-03-28 12:39 You should not name the function "Terminate" that does just introduce confusion... Rather call it SigTerminate SigTerminate first calls Terminat which actually sets the "Terminated" flag. -> and then it's time to send a message to unlock the loop ---------------------------------------------------------------------- Fred vS · 2023-03-28 14:17 Hello. You may try with this: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i     procedure TXRandREventWatcherThread.Execute;    var        RootWnd: TWindow;    begin      writeln(ClassName, '::Execute() started');        RootWnd := RootWindow(Display, DefaultScreen(Display));         XRRSelectInput(Display, RootWnd, FEventMask {NoEventMask});      while True do        begin        if XPending(Display) > 0  then         begin           XNextEvent(Display, @FLastEvent);           DebugLn('Recieved event: %d', [FLastEvent._type]);  // event 89 is change of resolution           Synchronize(@Notify);         end;     if Terminated then Break;               sleep(100);       end;       writeln(ClassName, '::Execute() ended');    end; And with this to destroy form: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i procedure TForm1.FormDestroy(Sender: TObject);begin  w.Terminate;  w.WaitFor;  // Add this  w.Free;end; ---------------------------------------------------------------------- MarkMLl · 2023-03-28 15:14 I broadly agree with Fred here, but would add a detail. I few months ago I was looking at something- I think it was custom code for handling TCP messages but forget the detail- and found it was convenient to put a message handler in a thread to handle incoming messages and marshal them into a queue. In order to terminate neatly I set up a single-thread HUP handler. That worked fine if the HUP was sent from the main thread, but not if it was sent from outside the program e.g. as a hint that a configuration file should be re-read. After much digging into documentation I learnt that that was more or less the expected behaviour. So even if you are in a thread, if you have to monitor the Terminated flag you need to take steps not to block on anything else: find out how to poll, and/or make sure that you can use a timeout on any function if necessary by using fpselect(). And I don't know where that leaves you with low-level X11 events :-/ MarkMLl ---------------------------------------------------------------------- artem101 · 2023-03-28 17:53 Quote from: Fred vS on March 28, 2023, 04:17:22 pmYou may try with this: It works! Thank you very much.