winemac: Implement ChangeDisplaySettingsEx().

This commit is contained in:
Ken Thomases 2013-02-17 19:28:49 -06:00 committed by Alexandre Julliard
parent 4f4ac0cafc
commit d26a6bf451
7 changed files with 422 additions and 8 deletions

View File

@ -48,11 +48,14 @@ @interface WineApplication : NSApplication <NSApplicationDelegate>
BOOL primaryScreenHeightValid;
NSMutableArray* orderedWineWindows;
NSMutableDictionary* originalDisplayModes;
}
@property (nonatomic) CGEventSourceKeyboardType keyboardType;
@property (readonly, copy, nonatomic) NSEvent* lastFlagsChanged;
@property (readonly, nonatomic) NSArray* orderedWineWindows;
@property (readonly, nonatomic) BOOL areDisplaysCaptured;
- (void) transformProcessToForeground;

View File

@ -51,7 +51,10 @@ - (id) init
keyWindows = [[NSMutableArray alloc] init];
orderedWineWindows = [[NSMutableArray alloc] init];
if (!eventQueues || !eventQueuesLock || !keyWindows || !orderedWineWindows)
originalDisplayModes = [[NSMutableDictionary alloc] init];
if (!eventQueues || !eventQueuesLock || !keyWindows || !orderedWineWindows ||
!originalDisplayModes)
{
[self release];
return nil;
@ -62,6 +65,7 @@ - (id) init
- (void) dealloc
{
[originalDisplayModes release];
[orderedWineWindows release];
[keyWindows release];
[eventQueues release];
@ -312,13 +316,14 @@ - (void) wineWindow:(WineWindow*)window
}
}
- (void) sendDisplaysChanged
- (void) sendDisplaysChanged:(BOOL)activating
{
macdrv_event event;
WineEventQueue* queue;
event.type = DISPLAYS_CHANGED;
event.window = NULL;
event.displays_changed.activating = activating;
[eventQueuesLock lock];
for (queue in eventQueues)
@ -326,6 +331,135 @@ - (void) sendDisplaysChanged
[eventQueuesLock unlock];
}
// We can compare two modes directly using CFEqual, but that may require that
// they are identical to a level that we don't need. In particular, when the
// OS switches between the integrated and discrete GPUs, the set of display
// modes can change in subtle ways. We're interested in whether two modes
// match in their most salient features, even if they aren't identical.
- (BOOL) mode:(CGDisplayModeRef)mode1 matchesMode:(CGDisplayModeRef)mode2
{
NSString *encoding1, *encoding2;
uint32_t ioflags1, ioflags2, different;
double refresh1, refresh2;
if (CGDisplayModeGetWidth(mode1) != CGDisplayModeGetWidth(mode2)) return FALSE;
if (CGDisplayModeGetHeight(mode1) != CGDisplayModeGetHeight(mode2)) return FALSE;
encoding1 = [(NSString*)CGDisplayModeCopyPixelEncoding(mode1) autorelease];
encoding2 = [(NSString*)CGDisplayModeCopyPixelEncoding(mode2) autorelease];
if (![encoding1 isEqualToString:encoding2]) return FALSE;
ioflags1 = CGDisplayModeGetIOFlags(mode1);
ioflags2 = CGDisplayModeGetIOFlags(mode2);
different = ioflags1 ^ ioflags2;
if (different & (kDisplayModeValidFlag | kDisplayModeSafeFlag | kDisplayModeStretchedFlag |
kDisplayModeInterlacedFlag | kDisplayModeTelevisionFlag))
return FALSE;
refresh1 = CGDisplayModeGetRefreshRate(mode1);
if (refresh1 == 0) refresh1 = 60;
refresh2 = CGDisplayModeGetRefreshRate(mode2);
if (refresh2 == 0) refresh2 = 60;
if (fabs(refresh1 - refresh2) > 0.1) return FALSE;
return TRUE;
}
- (CGDisplayModeRef)modeMatchingMode:(CGDisplayModeRef)mode forDisplay:(CGDirectDisplayID)displayID
{
CGDisplayModeRef ret = NULL;
NSArray *modes = [(NSArray*)CGDisplayCopyAllDisplayModes(displayID, NULL) autorelease];
for (id candidateModeObject in modes)
{
CGDisplayModeRef candidateMode = (CGDisplayModeRef)candidateModeObject;
if ([self mode:candidateMode matchesMode:mode])
{
ret = candidateMode;
break;
}
}
return ret;
}
- (BOOL) setMode:(CGDisplayModeRef)mode forDisplay:(CGDirectDisplayID)displayID
{
BOOL ret = FALSE;
NSNumber* displayIDKey = [NSNumber numberWithUnsignedInt:displayID];
CGDisplayModeRef currentMode, originalMode;
currentMode = CGDisplayCopyDisplayMode(displayID);
if (!currentMode) // Invalid display ID
return FALSE;
if ([self mode:mode matchesMode:currentMode]) // Already there!
{
CGDisplayModeRelease(currentMode);
return TRUE;
}
mode = [self modeMatchingMode:mode forDisplay:displayID];
if (!mode)
{
CGDisplayModeRelease(currentMode);
return FALSE;
}
originalMode = (CGDisplayModeRef)[originalDisplayModes objectForKey:displayIDKey];
if (!originalMode)
originalMode = currentMode;
if ([self mode:mode matchesMode:originalMode])
{
if ([originalDisplayModes count] == 1) // If this is the last changed display, do a blanket reset
{
CGRestorePermanentDisplayConfiguration();
CGReleaseAllDisplays();
[originalDisplayModes removeAllObjects];
ret = TRUE;
}
else // ... otherwise, try to restore just the one display
{
if (CGDisplaySetDisplayMode(displayID, mode, NULL) == CGDisplayNoErr)
{
[originalDisplayModes removeObjectForKey:displayIDKey];
ret = TRUE;
}
}
}
else
{
if ([originalDisplayModes count] || CGCaptureAllDisplays() == CGDisplayNoErr)
{
if (CGDisplaySetDisplayMode(displayID, mode, NULL) == CGDisplayNoErr)
{
[originalDisplayModes setObject:(id)originalMode forKey:displayIDKey];
ret = TRUE;
}
else if (![originalDisplayModes count])
{
CGRestorePermanentDisplayConfiguration();
CGReleaseAllDisplays();
}
}
}
CGDisplayModeRelease(currentMode);
if (ret)
{
[orderedWineWindows enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(id obj, NSUInteger idx, BOOL *stop){
[(WineWindow*)obj adjustWindowLevel];
}];
}
return ret;
}
- (BOOL) areDisplaysCaptured
{
return ([originalDisplayModes count] > 0);
}
/*
* ---------- NSApplication method overrides ----------
@ -349,12 +483,24 @@ - (void)applicationDidBecomeActive:(NSNotification *)notification
if ([window levelWhenActive] != [window level])
[window setLevel:[window levelWhenActive]];
}];
// If a Wine process terminates abruptly while it has the display captured
// and switched to a different resolution, Mac OS X will uncapture the
// displays and switch their resolutions back. However, the other Wine
// processes won't have their notion of the desktop rect changed back.
// This can lead them to refuse to draw or acknowledge clicks in certain
// portions of their windows.
//
// To solve this, we synthesize a displays-changed event whenever we're
// activated. This will provoke a re-synchronization of Wine's notion of
// the desktop rect with the actual state.
[self sendDisplaysChanged:TRUE];
}
- (void)applicationDidChangeScreenParameters:(NSNotification *)notification
{
primaryScreenHeightValid = FALSE;
[self sendDisplaysChanged];
[self sendDisplaysChanged:FALSE];
}
- (void)applicationDidResignActive:(NSNotification *)notification
@ -512,3 +658,18 @@ void macdrv_beep(void)
NSBeep();
});
}
/***********************************************************************
* macdrv_set_display_mode
*/
int macdrv_set_display_mode(const struct macdrv_display* display,
CGDisplayModeRef display_mode)
{
__block int ret;
OnMainThread(^{
ret = [NSApp setMode:display_mode forDisplay:display->displayID];
});
return ret;
}

View File

@ -62,4 +62,6 @@ @interface WineWindow : NSPanel <NSWindowDelegate>
@property (readonly, nonatomic) BOOL floating;
@property (readonly, nonatomic) NSInteger levelWhenActive;
- (void) adjustWindowLevel;
@end

View File

@ -338,17 +338,22 @@ - (void) setWindowFeatures:(const struct macdrv_window_features*)wf
- (void) adjustWindowLevel
{
NSInteger level;
BOOL fullscreen;
BOOL fullscreen, captured;
NSScreen* screen;
NSUInteger index;
WineWindow* other = nil;
screen = screen_covered_by_rect([self frame], [NSScreen screens]);
fullscreen = (screen != nil);
captured = (screen || [self screen]) && [NSApp areDisplaysCaptured];
if (fullscreen)
if (captured || fullscreen)
{
level = NSMainMenuWindowLevel + 1;
if (captured)
level = CGShieldingWindowLevel() + 1; /* Need +1 or we don't get mouse moves */
else
level = NSMainMenuWindowLevel + 1;
if (self.floating)
level++;
}

View File

@ -28,7 +28,11 @@
WINE_DEFAULT_DEBUG_CHANNEL(display);
BOOL CDECL macdrv_EnumDisplaySettingsEx(LPCWSTR devname, DWORD mode, LPDEVMODEW devmode, DWORD flags);
static CFArrayRef modes;
static int default_mode_bpp;
static CRITICAL_SECTION modes_section;
static CRITICAL_SECTION_DEBUG critsect_debug =
{
@ -117,6 +121,40 @@ static BOOL read_registry_settings(DEVMODEW *dm)
}
static BOOL write_registry_settings(const DEVMODEW *dm)
{
char wine_mac_reg_key[128];
HKEY hkey;
BOOL ret = TRUE;
if (!get_display_device_reg_key(wine_mac_reg_key, sizeof(wine_mac_reg_key)))
return FALSE;
if (RegCreateKeyExA(HKEY_CURRENT_CONFIG, wine_mac_reg_key, 0, NULL,
REG_OPTION_VOLATILE, KEY_WRITE, NULL, &hkey, NULL))
return FALSE;
#define set_value(name, data) \
if (RegSetValueExA(hkey, name, 0, REG_DWORD, (const BYTE*)(data), sizeof(DWORD))) \
ret = FALSE
set_value("DefaultSettings.BitsPerPel", &dm->dmBitsPerPel);
set_value("DefaultSettings.XResolution", &dm->dmPelsWidth);
set_value("DefaultSettings.YResolution", &dm->dmPelsHeight);
set_value("DefaultSettings.VRefresh", &dm->dmDisplayFrequency);
set_value("DefaultSettings.Flags", &dm->dmDisplayFlags);
set_value("DefaultSettings.XPanning", &dm->dmPosition.x);
set_value("DefaultSettings.YPanning", &dm->dmPosition.y);
set_value("DefaultSettings.Orientation", &dm->dmDisplayOrientation);
set_value("DefaultSettings.FixedOutput", &dm->dmDisplayFixedOutput);
#undef set_value
RegCloseKey(hkey);
return ret;
}
static int display_mode_bits_per_pixel(CGDisplayModeRef display_mode)
{
CFStringRef pixel_encoding;
@ -153,6 +191,202 @@ static int display_mode_bits_per_pixel(CGDisplayModeRef display_mode)
}
static int get_default_bpp(void)
{
int ret;
EnterCriticalSection(&modes_section);
if (!default_mode_bpp)
{
CGDisplayModeRef mode = CGDisplayCopyDisplayMode(kCGDirectMainDisplay);
if (mode)
{
default_mode_bpp = display_mode_bits_per_pixel(mode);
CFRelease(mode);
}
if (!default_mode_bpp)
default_mode_bpp = 32;
}
ret = default_mode_bpp;
LeaveCriticalSection(&modes_section);
TRACE(" -> %d\n", ret);
return ret;
}
/***********************************************************************
* ChangeDisplaySettingsEx (MACDRV.@)
*
*/
LONG CDECL macdrv_ChangeDisplaySettingsEx(LPCWSTR devname, LPDEVMODEW devmode,
HWND hwnd, DWORD flags, LPVOID lpvoid)
{
LONG ret = DISP_CHANGE_BADMODE;
int bpp;
DEVMODEW dm;
BOOL def_mode = TRUE;
struct macdrv_display *displays;
int num_displays;
CFArrayRef display_modes;
CFIndex count, i, safe;
TRACE("%s %p %p 0x%08x %p\n", debugstr_w(devname), devmode, hwnd, flags, lpvoid);
if (devmode)
{
/* this is the minimal dmSize that XP accepts */
if (devmode->dmSize < FIELD_OFFSET(DEVMODEW, dmFields))
return DISP_CHANGE_FAILED;
if (devmode->dmSize >= FIELD_OFFSET(DEVMODEW, dmFields) + sizeof(devmode->dmFields))
{
if (((devmode->dmFields & DM_BITSPERPEL) && devmode->dmBitsPerPel) ||
((devmode->dmFields & DM_PELSWIDTH) && devmode->dmPelsWidth) ||
((devmode->dmFields & DM_PELSHEIGHT) && devmode->dmPelsHeight) ||
((devmode->dmFields & DM_DISPLAYFREQUENCY) && devmode->dmDisplayFrequency))
def_mode = FALSE;
}
}
if (def_mode)
{
if (!macdrv_EnumDisplaySettingsEx(devname, ENUM_REGISTRY_SETTINGS, &dm, 0))
{
ERR("Default mode not found!\n");
return DISP_CHANGE_BADMODE;
}
TRACE("Return to original display mode\n");
devmode = &dm;
}
if ((devmode->dmFields & (DM_PELSWIDTH | DM_PELSHEIGHT)) != (DM_PELSWIDTH | DM_PELSHEIGHT))
{
WARN("devmode doesn't specify the resolution: %04x\n", devmode->dmFields);
return DISP_CHANGE_BADMODE;
}
if (macdrv_get_displays(&displays, &num_displays))
return DISP_CHANGE_FAILED;
display_modes = CGDisplayCopyAllDisplayModes(displays[0].displayID, NULL);
if (!display_modes)
{
macdrv_free_displays(displays);
return DISP_CHANGE_FAILED;
}
bpp = get_default_bpp();
if ((devmode->dmFields & DM_BITSPERPEL) && devmode->dmBitsPerPel != bpp)
TRACE("using default %d bpp instead of caller's request %d bpp\n", bpp, devmode->dmBitsPerPel);
TRACE("looking for %dx%dx%dbpp @%d Hz",
(devmode->dmFields & DM_PELSWIDTH ? devmode->dmPelsWidth : 0),
(devmode->dmFields & DM_PELSHEIGHT ? devmode->dmPelsHeight : 0),
bpp,
(devmode->dmFields & DM_DISPLAYFREQUENCY ? devmode->dmDisplayFrequency : 0));
if (devmode->dmFields & DM_DISPLAYFIXEDOUTPUT)
TRACE(" %sstretched", devmode->dmDisplayFixedOutput == DMDFO_STRETCH ? "" : "un");
if (devmode->dmFields & DM_DISPLAYFLAGS)
TRACE(" %sinterlaced", devmode->dmDisplayFlags & DM_INTERLACED ? "" : "non-");
TRACE("\n");
safe = -1;
count = CFArrayGetCount(display_modes);
for (i = 0; i < count; i++)
{
CGDisplayModeRef display_mode = (CGDisplayModeRef)CFArrayGetValueAtIndex(display_modes, i);
uint32_t io_flags = CGDisplayModeGetIOFlags(display_mode);
int mode_bpp = display_mode_bits_per_pixel(display_mode);
size_t width = CGDisplayModeGetWidth(display_mode);
size_t height = CGDisplayModeGetHeight(display_mode);
if (!(io_flags & kDisplayModeValidFlag) || !(io_flags & kDisplayModeSafeFlag))
continue;
safe++;
if (bpp != mode_bpp)
continue;
if (devmode->dmFields & DM_PELSWIDTH)
{
if (devmode->dmPelsWidth != width)
continue;
}
if (devmode->dmFields & DM_PELSHEIGHT)
{
if (devmode->dmPelsHeight != height)
continue;
}
if ((devmode->dmFields & DM_DISPLAYFREQUENCY) && devmode->dmDisplayFrequency != 0)
{
double refresh_rate = CGDisplayModeGetRefreshRate(display_mode);
if (!refresh_rate)
refresh_rate = 60;
if (devmode->dmDisplayFrequency != (DWORD)refresh_rate)
continue;
}
if (devmode->dmFields & DM_DISPLAYFIXEDOUTPUT)
{
if (!(devmode->dmDisplayFixedOutput == DMDFO_STRETCH) != !(io_flags & kDisplayModeStretchedFlag))
continue;
}
if (devmode->dmFields & DM_DISPLAYFLAGS)
{
if (!(devmode->dmDisplayFlags & DM_INTERLACED) != !(io_flags & kDisplayModeInterlacedFlag))
continue;
}
/* we have a valid mode */
TRACE("Requested display settings match mode %ld\n", safe);
if ((flags & CDS_UPDATEREGISTRY) && !write_registry_settings(devmode))
{
WARN("Failed to update registry\n");
ret = DISP_CHANGE_NOTUPDATED;
break;
}
if (flags & (CDS_TEST | CDS_NORESET))
ret = DISP_CHANGE_SUCCESSFUL;
else
{
if (macdrv_set_display_mode(&displays[0], display_mode))
{
SendMessageW(GetDesktopWindow(), WM_MACDRV_UPDATE_DESKTOP_RECT, mode_bpp,
MAKELPARAM(width, height));
ret = DISP_CHANGE_SUCCESSFUL;
}
else
{
WARN("Failed to set display mode\n");
ret = DISP_CHANGE_FAILED;
}
}
break;
}
CFRelease(display_modes);
macdrv_free_displays(displays);
if (i >= count)
{
/* no valid modes found */
ERR("No matching mode found %ux%ux%d @%u!\n", devmode->dmPelsWidth, devmode->dmPelsHeight,
bpp, devmode->dmDisplayFrequency);
}
return ret;
}
/***********************************************************************
* EnumDisplayMonitors (MACDRV.@)
*/
@ -426,8 +660,11 @@ void macdrv_displays_changed(const macdrv_event *event)
/* A system display change will get delivered to all GUI-attached threads,
so the desktop-window-owning thread will get it and all others should
ignore it. */
if (GetWindowThreadProcessId(hwnd, NULL) == GetCurrentThreadId())
ignore it. A synthesized display change event due to activation
will only get delivered to the activated process. So, it needs to
process it (by sending it to the desktop window). */
if (event->displays_changed.activating ||
GetWindowThreadProcessId(hwnd, NULL) == GetCurrentThreadId())
{
CGDirectDisplayID mainDisplay = CGMainDisplayID();
CGDisplayModeRef mode = CGDisplayCopyDisplayMode(mainDisplay);

View File

@ -121,6 +121,8 @@
/* display */
extern int macdrv_get_displays(struct macdrv_display** displays, int* count) DECLSPEC_HIDDEN;
extern void macdrv_free_displays(struct macdrv_display* displays) DECLSPEC_HIDDEN;
extern int macdrv_set_display_mode(const struct macdrv_display* display,
CGDisplayModeRef display_mode) DECLSPEC_HIDDEN;
/* event */
@ -149,6 +151,9 @@
int type;
macdrv_window window;
union {
struct {
int activating;
} displays_changed;
struct {
CGKeyCode keycode;
CGEventFlags modifiers;

View File

@ -6,6 +6,7 @@
@ cdecl ActivateKeyboardLayout(long long) macdrv_ActivateKeyboardLayout
@ cdecl Beep() macdrv_Beep
@ cdecl ChangeDisplaySettingsEx(ptr ptr long long long) macdrv_ChangeDisplaySettingsEx
@ cdecl CreateDesktopWindow(long) macdrv_CreateDesktopWindow
@ cdecl CreateWindow(long) macdrv_CreateWindow
@ cdecl DestroyWindow(long) macdrv_DestroyWindow