Forum > Linux
[Solved] Get notified when monitors count/resolution changed
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