Forum > Linux

[Solved] Get notified when monitors count/resolution changed

(1/2) > >>

artem101:
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  [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---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  [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---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.UpdateMonitors;   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:
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( <params to be filled > );
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:

--- Quote from: mikerabat on March 27, 2023, 10:58:12 am ---        XSendEvent( <params to be filled > );

--- End quote ---

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:
I add sending custom message, but still doesn't work.


--- Code: Pascal  [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---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:
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

Navigation

[0] Message Index

[#] Next page

Go to full version