I'm building a taskbar for an X11 desktop and so far I've been successful in detecting new and removed windows and changes to window titles and icons.
However, despite setting every event mask I can think of on the client windows, I've been unable to get any events when a test application adds the _NET_WM_STATE_DEMANDS_ATTENTION atom to its _NET_WM_STATE property.
I'm using Qt5 and capturing the incoming X11 events using installNativeEventFilter. However, I've also tried using xprop -spy and I see the same issue there: Even though polling the _NET_WM_STATE property shows the atom being added and removed, no property change event is ever received. Fluxbox also doesn't seem to pick up on it until something else causes it to re-query the window.
My event filter code resembles this:
xcb_generic_event_t* ev = static_cast<xcb_generic_event_t*>(message);
uint32_t type = ev->response_type;
switch (type) {
case XCB_PROPERTY_NOTIFY: {
xcb_property_notify_event_t* pev =
reinterpret_cast<xcb_property_notify_event_t*>(ev);
qDebug() << "property" << pev->window << pev->atom << (int)pev->state;
break;
/* snip */
default:
qDebug() << "unrecognized event" << type;
};
My test application uses QApplication::alert() on a timer to assert the attention flag.
Is there some special handling necessary for atom list properties? Am I doomed to poll for changes? I've tried looking at the source code for other window managers but I haven't been able to identify any specific differences.
It turns out that Qt5's native event filter on X11 doesn't pass everything through consistently. I haven't isolated that bug yet, but I wrote my own minimalist xcb event loop on a separate connection to handle window-management activities and it works fine.
Related
I'm tinkering with X11 using Go and Xlib (using cgo) and I'm trying to program basic window management functionalities, but I'm having problems with input management (keyboard in this case).
So far I've created a basic reparenting window manager where client windows (i.e. windows managed by this program) are wrapped within container windows created by this program. Container windows are direct children of root window and client windows are direct children of their container windows.
I'm trying to grab the entire keyboard and check if a key event is directed towards the window manager in order to process certain things. In case this key event is not something related to the window manager I would like to pass it along to client windows. I know that there is the option to select only specific keys or modifiers (using XGrabKey) for this matter (I was able to do it), but I would like to be able to grab the entire keyboard in this case.
So far I have the following code which doesn't work and keys are not passed to client windows.
...
C.XGrabKeyboard(
display,
rootWindow,
0,
C.GrabModeAsync,
C.GrabModeAsync,
C.CurrentTime,
)
...
for {
var event C.XEvent
C.XNextEvent(display, &event)
eventType := (*C.int)(unsafe.Pointer(&event[0]))
switch *eventType {
...
case C.KeyPress:
eventPayload := (*C.XKeyEvent)(unsafe.Pointer(&event[0]))
// Value of eventPayload.root here equals rootWindow.
// Value of eventPayload.window here equals rootWindow.
// Value of eventPayload.subwindow here equals a ContainerWindow.
if SOME_CONDITIONS_ARE_MET {
// Key event is directed towards the window manager. Process this key event.
...
continue
}
// Window manager has nothing to do with this key event. Pass it along.
C.XAllowEvents(display, C.ReplayKeyboard, C.CurrentTime)
...
}
}
It is worth mentioning that in this case I've used C.XSynchronize(display, 1) so calling XSync is no longer required. Also calling XFlush after XAllowEvents did not solve the problem either.
By the way I originally saw the XAllowEvents solution in this Stack Overflow question and this website.
In the VS2019 project template for C++/CX D3D12, the DisplayInformation::DisplayContentsInvalidated event (documented here) is subscribed and when this event fires, the code in the project template attempts to validate the D3D12 device. The criteria being used to validate is whether the default adapter has changed:
// This method is called in the event handler for the DisplayContentsInvalidated event.
void DX::DeviceResources::ValidateDevice()
{
// The D3D Device is no longer valid if the default adapter changed since the device
// was created or if the device has been removed.
// First, get the LUID for the default adapter from when the device was created.
DXGI_ADAPTER_DESC previousDesc;
{
ComPtr<IDXGIAdapter1> previousDefaultAdapter;
DX::ThrowIfFailed(m_dxgiFactory->EnumAdapters1(0, &previousDefaultAdapter));
DX::ThrowIfFailed(previousDefaultAdapter->GetDesc(&previousDesc));
}
// Next, get the information for the current default adapter.
DXGI_ADAPTER_DESC currentDesc;
{
ComPtr<IDXGIFactory4> currentDxgiFactory;
DX::ThrowIfFailed(CreateDXGIFactory1(IID_PPV_ARGS(¤tDxgiFactory)));
ComPtr<IDXGIAdapter1> currentDefaultAdapter;
DX::ThrowIfFailed(currentDxgiFactory->EnumAdapters1(0, ¤tDefaultAdapter));
DX::ThrowIfFailed(currentDefaultAdapter->GetDesc(¤tDesc));
}
// If the adapter LUIDs don't match, or if the device reports that it has been removed,
// a new D3D device must be created.
if (previousDesc.AdapterLuid.LowPart != currentDesc.AdapterLuid.LowPart ||
previousDesc.AdapterLuid.HighPart != currentDesc.AdapterLuid.HighPart ||
FAILED(m_d3dDevice->GetDeviceRemovedReason()))
{
m_deviceRemoved = true;
}
}
However, it seems to me that the default adapter may not be the elected adapter, as you can see in this code, because it ensures the adapter can create a D3D12 device before electing it, and this same check does not exist in the validation code above:
// This method acquires the first available hardware adapter that supports Direct3D 12.
// If no such adapter can be found, *ppAdapter will be set to nullptr.
void DX::DeviceResources::GetHardwareAdapter(IDXGIAdapter1** ppAdapter)
{
ComPtr<IDXGIAdapter1> adapter;
*ppAdapter = nullptr;
for (UINT adapterIndex = 0; DXGI_ERROR_NOT_FOUND != m_dxgiFactory->EnumAdapters1(adapterIndex, &adapter); adapterIndex++)
{
DXGI_ADAPTER_DESC1 desc;
adapter->GetDesc1(&desc);
if (desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE)
{
// Don't select the Basic Render Driver adapter.
continue;
}
// Check to see if the adapter supports Direct3D 12, but don't create the
// actual device yet.
if (SUCCEEDED(D3D12CreateDevice(adapter.Get(), D3D_FEATURE_LEVEL_11_0, _uuidof(ID3D12Device), nullptr)))
{
break;
}
}
*ppAdapter = adapter.Detach();
}
Is this unintentionally relying on the default adapter being the elected adapter, or are there wider assumptions we can make based on the default adapter having changed?
What is the real meaning if DisplayInformation::DisplayContentsInvalidated? What is this telling me? Why does this suggest the device is possibly invalid? What are the cases where this event fires andthe device would or would not be invalid?
And since we are also checking for a DeviceRemovedReason here, are there cases where the default adapter doesn't change but the device was removed because of DisplayInformation::DisplayContentsInvalidated?
Do we even really need to handle this at all? What would happen if I just completely ignored this event and kept trying to render? Would I not end up handling whatever problem this event firing is indicating in the documented "device lost scenarios" per this document?
We are still investigating your question and get some information.
With regards to when the DisplayContentInvalidated Event will be triggered, it is typically invoked upon various activities including changing of the primary display or if the display configurations have changed in any other way. This would also include if there were certain types of issues occurring with the graphics, which this event should be able to be utilized to handle such situations.
With regards to the template, we ran some tests on our end and we indeed did see that the project uses the first adapter that it finds that can handle DirectX12 rather than an elected adapter. Upon further tests, we removed that particular check and found that the application does indeed continue to run as expected. The adapter may not be the one that was used to create the device at application startup. So indeed, this check does seem like it can potentially be ignored as indicated. However, having this event in the code might not hurt as it can provide the extra functionality should it be required for any situation.
Any other detailed information would likely be found in our documentation regarding this. However, it seems that the template is meant for demonstrating certain functionalities of DirextX12 and its various API’s so ignoring the DisplayContentInvalidated Event should typically not cause any issues.
I am writing an application for my own purposes that aims to get play pause events no matter what is going on in the system. I have gotten this much working
let commandCenter = MPRemoteCommandCenter.shared()
commandCenter.togglePlayPauseCommand.isEnabled = true
commandCenter.togglePlayPauseCommand.addTarget { (MPRemoteCommandEvent) -> MPRemoteCommandHandlerStatus in
print("Play Pause Command")
return .success
}
commandCenter.nextTrackCommand.isEnabled = true
commandCenter.nextTrackCommand.addTarget { (MPRemoteCommandEvent) -> MPRemoteCommandHandlerStatus in
print("NextTrackCommand")
return .success
}
commandCenter.previousTrackCommand.isEnabled = true
commandCenter.previousTrackCommand.addTarget { (MPRemoteCommandEvent) -> MPRemoteCommandHandlerStatus in
print("previousTrackCommand")
return .success
}
commandCenter.playCommand.isEnabled = true
commandCenter.playCommand.addTarget { (MPRemoteCommandEvent) -> MPRemoteCommandHandlerStatus in
print("playCommand")
return .success
}
MPNowPlayingInfoCenter.default().playbackState = .playing
Most of those methods are there because apparently you will not get any notifications without having nextTrackCommand or previousTrackCommand or playCommand implemented.
Anyways my one issue is that as soon as you open another application that uses audio these event handlers stop getting called and I cant find a way to detect and fix this.
I would normally try doing AVAudioSession things to state this as a background application however that does not seem to work. Any ideas on how I can get playpause events no matter what state the system is in?
I would like to be able to always listen for these events OR get an indication of when someone else has taken control of the audio? Perhaps even be able to re-subscribe to these play pause events.
There's an internal queue in the system which contains all the audio event subscribers. Other applications get on top of it when you start using them.
I would like to be able to always listen for these events
There's no API for that but there's a dirty workaround. If I understand your issue correctly, this snippet:
MPNowPlayingInfoCenter.default().playbackState = .paused
MPNowPlayingInfoCenter.default().playbackState = .playing
must do the trick for you if you run it in a loop somewhere in your application.
Note that this is not 100% reliable because:
If an event is generated before two subsequent playbackState state changes right after you've switched to a different application, it would still be catched by the application in the active window;
If another application is doing the same thing, there would be a constant race condition in the queue, with unpredictable outcome.
References:
Documentation for playbackState is here;
See also a similar question;
See also a bug report for mpv with a similar
issue (a pre-MPRemoteCommandCenter one, but still very valuable)
OR get an indication of when someone else has taken control of the audio
As far as I know there's no public API for this in macOS.
Does anyone know how to detect when the user changes the current input source in OSX?
I can call TISCopyCurrentKeyboardInputSource() to find out which input source ID is being used like this:
TISInputSourceRef isource = TISCopyCurrentKeyboardInputSource();
if ( isource == NULL )
{
cerr << "Couldn't get the current input source\n.";
return -1;
}
CFStringRef id = (CFStringRef)TISGetInputSourceProperty(
isource,
kTISPropertyInputSourceID);
CFRelease(isource);
If my input source is "German", then id ends up being "com.apple.keylayout.German", which is mostly what I want. Except:
The results of TISCopyCurrentKeyboardInputSource() doesn't change once my process starts? In particular, I can call TISCopyCurrentKeyboardInputSource() in a loop and switch my input source, but TISCopyCurrentKeyboardInputSource() keeps returning the input source that my process started with.
I'd really like to be notified when the input source changes. Is there any way of doing this? To get a notification or an event of some kind telling me that the input source has been changed?
You can observe the NSTextInputContextKeyboardSelectionDidChangeNotification notification posted by NSTextInputContext to the default Cocoa notification center. Alternatively, you can observe the kTISNotifySelectedKeyboardInputSourceChanged notification delivered via the Core Foundation distributed notification center.
However, any such change starts in a system process external to your app. The system then notifies the frameworks in each app process. The frameworks can only receive such notifications when it is allowed to run its event loop. Likewise, if you're observing the distributed notification yourself, that can only happen when the event loop (or at least the main thread's run loop) is allowed to run.
So, that explains why running a loop which repeatedly checks the result of TISCopyCurrentKeyboardInputSource() doesn't work. You're not allowing the frameworks to monitor the channel over which it would be informed of the change. If, rather than a loop, you were to use a repeating timer with a low enough frequency that other stuff has a chance to run, and you returned control to the app's event loop, you would see the result of TISCopyCurrentKeyboardInputSource() changing.
I have a program that switches desktop and start a new process on it. When the process exits, the parent process restores the original desktop.
For testing purposes, I put a button in a plain win32 app that triggers the switch. It works, and closing the launched process (notepad), I go back to the original desktop.
In that same program, I have called WTSRegisterSessionNotification to receive a notification when a session is unlocked (WTS_SESSION_UNLOCK). I receive it.
But when I try to switch desktops in WTS_SESSION_UNLOCK message handler, SwitchDesktop fails and GetLastError is 0. The documentation says that last error is usually not set by SwitchDesktop.
Funny enough, if I put my call to switch desktop in a for loop, it works on the 5th iteration.
In short, this does not work :
case WM_WTSSESSION_CHANGE:
if(wParam == WTS_SESSION_UNLOCK)
{
SwitchDesktop(a_valid_desktop_handle);
}
break;
But this ugly hack works :
case WM_WTSSESSION_CHANGE:
if(wParam == WTS_SESSION_UNLOCK)
{
for(int i=0; i<10; ++i)
{
if(SwitchDesktop(a_valid_desktop_handle))
{
//This will work when i == 5, maybe 6.
break;
}
}
}
break;
Setting a timer (to exit the message loop) also works, but it is just a more convoluted form of loop with regards to this problem. SwitchDesktop will work on after a handfull of WM_TIMER messages. It looks like constant time, although I did not measure it.
MSDN documentation for SwitchDesktop mentions that this will fail with a custom Userinit process, which I use. But getting the name of the current desktop just before the switch :
wchar_t name[512];
GetUserObjectInformation(GetThreadDesktop(GetCurrentThreadId()), UOI_NAME, name, sizeof(name)/sizeof(*name), 0);
OutputDebugString(name);
Gives me default all the time. And since GetLastError is 0, not 5 (access denied) I am pretty sure the secure desktop is gone before I receive the WTS_SESSION_UNLOCK notification.
I known I can't switch desktop while the screen is locked, but is there a "grace period" after the desktop is unlocked in which I can't call SwitchDesktop ?
When the desktop gets locked, it switches to another desktop which is reserved for this purpose. It is quite possible that when you receive the message, that desktop is still in control and you're not allowed to switch because you're not running in the current desktop.
I can't test it right now but I would put the call to SwitchDesktop not on WTS_SESSION_UNLOCK but on WTS_CONSOLE_CONNECT. From what I gather WTS_SESSION_UNLOCK occurs first and then your get WTS_CONSOLE_CONNECT which would correspond to what you see with the "constand time"...
SwitchDesktop fails (with error 0) because (accordingly to MSDN) it belongs to a window station not (yet) visible. There is no user notification I know of that says "the HWINSTA become visible".