mirror of https://github.com/bsnes-emu/bsnes.git
v108.10
* provide actual display device names in ruby::Video::hasMonitors() * macOS: fixed LineEdit::onChange (fixes state manager state naming) * macOS: fixed RadioLabel height to not clip off bottom of radio icons * macOS: fixed RadioLabel initial check states (fixed input settings focus mode options) * macOS: fixed TableViewItem::setSelected (fixes initial selection on settings/tools windows) * macOS: fixed TextEdit geometry (fixed manifest viewer) * macOS: don't allow multiple TableViewItems to be selected in single-selection mode ** (fixes settings/tools windows selecting multiple items via menubar options)
This commit is contained in:
parent
04b85ade6b
commit
5ba538ee39
|
@ -29,7 +29,7 @@ using namespace nall;
|
||||||
|
|
||||||
namespace Emulator {
|
namespace Emulator {
|
||||||
static const string Name = "bsnes";
|
static const string Name = "bsnes";
|
||||||
static const string Version = "108.9";
|
static const string Version = "108.10";
|
||||||
static const string Author = "byuu";
|
static const string Author = "byuu";
|
||||||
static const string License = "GPLv3";
|
static const string License = "GPLv3";
|
||||||
static const string Website = "https://byuu.org";
|
static const string Website = "https://byuu.org";
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
-(IBAction) activate:(id)sender {
|
-(IBAction) activate:(id)sender {
|
||||||
|
lineEdit->state.text = [[self stringValue] UTF8String];
|
||||||
lineEdit->doActivate();
|
lineEdit->doActivate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,7 @@ auto pRadioLabel::construct() -> void {
|
||||||
cocoaView = cocoaRadioLabel = [[CocoaRadioLabel alloc] initWith:self()];
|
cocoaView = cocoaRadioLabel = [[CocoaRadioLabel alloc] initWith:self()];
|
||||||
pWidget::construct();
|
pWidget::construct();
|
||||||
|
|
||||||
if(state().checked) setChecked();
|
setGroup(state().group);
|
||||||
setText(state().text);
|
setText(state().text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -41,24 +41,11 @@ auto pRadioLabel::destruct() -> void {
|
||||||
|
|
||||||
auto pRadioLabel::minimumSize() const -> Size {
|
auto pRadioLabel::minimumSize() const -> Size {
|
||||||
Size size = pFont::size(self().font(true), state().text);
|
Size size = pFont::size(self().font(true), state().text);
|
||||||
return {size.width() + 22, size.height()};
|
return {size.width() + 22, size.height() + 2};
|
||||||
}
|
}
|
||||||
|
|
||||||
auto pRadioLabel::setChecked() -> void {
|
auto pRadioLabel::setChecked() -> void {
|
||||||
@autoreleasepool {
|
setGroup(state().group);
|
||||||
if(auto group = state().group) {
|
|
||||||
for(auto& weak : group->state.objects) {
|
|
||||||
if(auto object = weak.acquire()) {
|
|
||||||
if(auto self = object->self()) {
|
|
||||||
if(auto p = dynamic_cast<pRadioLabel*>(self)) {
|
|
||||||
auto state = this == p ? NSOnState : NSOffState;
|
|
||||||
[p->cocoaView setState:state];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto pRadioLabel::setGeometry(Geometry geometry) -> void {
|
auto pRadioLabel::setGeometry(Geometry geometry) -> void {
|
||||||
|
@ -66,9 +53,26 @@ auto pRadioLabel::setGeometry(Geometry geometry) -> void {
|
||||||
geometry.x() - 1, geometry.y(),
|
geometry.x() - 1, geometry.y(),
|
||||||
geometry.width() + 2, geometry.height()
|
geometry.width() + 2, geometry.height()
|
||||||
});
|
});
|
||||||
|
//buttonType:NSRadioButton does not set initial icon via programmatically calling setState:NSOnState.
|
||||||
|
//I can only get the icon to show as checked initially by setting the state on geometry resizes.
|
||||||
|
//adjusting the initWithFrame:NSMakeRect did not help.
|
||||||
|
if(state().checked) setChecked();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto pRadioLabel::setGroup(sGroup group) -> void {
|
auto pRadioLabel::setGroup(sGroup group) -> void {
|
||||||
|
@autoreleasepool {
|
||||||
|
if(!group) return;
|
||||||
|
for(auto& weak : group->state.objects) {
|
||||||
|
if(auto object = weak.acquire()) {
|
||||||
|
if(auto self = object->self()) {
|
||||||
|
if(auto p = dynamic_cast<pRadioLabel*>(self)) {
|
||||||
|
auto state = p->state().checked ? NSOnState : NSOffState;
|
||||||
|
[p->cocoaView setState:state];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto pRadioLabel::setText(const string& text) -> void {
|
auto pRadioLabel::setText(const string& text) -> void {
|
||||||
|
|
|
@ -37,6 +37,17 @@ auto pTableViewItem::setForegroundColor(Color color) -> void {
|
||||||
}
|
}
|
||||||
|
|
||||||
auto pTableViewItem::setSelected(bool selected) -> void {
|
auto pTableViewItem::setSelected(bool selected) -> void {
|
||||||
|
@autoreleasepool {
|
||||||
|
if(auto tableView = _parent()) {
|
||||||
|
auto lock = tableView->acquire();
|
||||||
|
auto indexSet = [[NSMutableIndexSet alloc] init];
|
||||||
|
for(auto& item : tableView->state().items) {
|
||||||
|
if(item->selected()) [indexSet addIndex:item->offset()];
|
||||||
|
}
|
||||||
|
[[tableView->cocoaView content] selectRowIndexes:indexSet byExtendingSelection:NO];
|
||||||
|
[indexSet release];
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto pTableViewItem::_parent() -> maybe<pTableView&> {
|
auto pTableViewItem::_parent() -> maybe<pTableView&> {
|
||||||
|
|
|
@ -104,6 +104,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
-(void) tableViewSelectionDidChange:(NSNotification*)notification {
|
-(void) tableViewSelectionDidChange:(NSNotification*)notification {
|
||||||
|
if(tableView->self()->locked()) return;
|
||||||
for(auto& tableViewItem : tableView->state.items) {
|
for(auto& tableViewItem : tableView->state.items) {
|
||||||
tableViewItem->state.selected = tableViewItem->offset() == [content selectedRow];
|
tableViewItem->state.selected = tableViewItem->offset() == [content selectedRow];
|
||||||
}
|
}
|
||||||
|
|
|
@ -87,6 +87,11 @@ auto pTextEdit::setFont(const Font& font) -> void {
|
||||||
auto pTextEdit::setForegroundColor(Color color) -> void {
|
auto pTextEdit::setForegroundColor(Color color) -> void {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto pTextEdit::setGeometry(Geometry geometry) -> void {
|
||||||
|
pWidget::setGeometry(geometry);
|
||||||
|
[cocoaView configure];
|
||||||
|
}
|
||||||
|
|
||||||
auto pTextEdit::setText(const string& text) -> void {
|
auto pTextEdit::setText(const string& text) -> void {
|
||||||
@autoreleasepool {
|
@autoreleasepool {
|
||||||
[[cocoaView content] setString:[NSString stringWithUTF8String:text]];
|
[[cocoaView content] setString:[NSString stringWithUTF8String:text]];
|
||||||
|
|
|
@ -21,6 +21,7 @@ struct pTextEdit : pWidget {
|
||||||
auto setEnabled(bool enabled) -> void override;
|
auto setEnabled(bool enabled) -> void override;
|
||||||
auto setFont(const Font& font) -> void override;
|
auto setFont(const Font& font) -> void override;
|
||||||
auto setForegroundColor(Color color) -> void;
|
auto setForegroundColor(Color color) -> void;
|
||||||
|
auto setGeometry(Geometry geometry) -> void override;
|
||||||
auto setText(const string& text) -> void;
|
auto setText(const string& text) -> void;
|
||||||
auto setTextCursor(TextCursor textCursor) -> void;
|
auto setTextCursor(TextCursor textCursor) -> void;
|
||||||
auto setWordWrap(bool wordWrap) -> void;
|
auto setWordWrap(bool wordWrap) -> void;
|
||||||
|
|
|
@ -100,6 +100,12 @@ auto mTableViewItem::setParent(mObject* parent, signed offset) -> type& {
|
||||||
}
|
}
|
||||||
|
|
||||||
auto mTableViewItem::setSelected(bool selected) -> type& {
|
auto mTableViewItem::setSelected(bool selected) -> type& {
|
||||||
|
//if in single-selection mode, selecting one item must deselect all other items in the TableView
|
||||||
|
if(auto parent = parentTableView()) {
|
||||||
|
if(!parent->state.batchable && selected) {
|
||||||
|
for(auto& item : parent->state.items) item->state.selected = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
state.selected = selected;
|
state.selected = selected;
|
||||||
signal(setSelected, selected);
|
signal(setSelected, selected);
|
||||||
return *this;
|
return *this;
|
||||||
|
|
|
@ -54,6 +54,7 @@ ifeq ($(platform),windows)
|
||||||
endif
|
endif
|
||||||
|
|
||||||
ifeq ($(platform),macos)
|
ifeq ($(platform),macos)
|
||||||
|
ruby.options += -framework IOKit
|
||||||
ruby.options += $(if $(findstring audio.openal,$(ruby)),-framework OpenAL)
|
ruby.options += $(if $(findstring audio.openal,$(ruby)),-framework OpenAL)
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,9 @@ using namespace ruby;
|
||||||
#include <nall/macos/guard.hpp>
|
#include <nall/macos/guard.hpp>
|
||||||
#include <Cocoa/Cocoa.h>
|
#include <Cocoa/Cocoa.h>
|
||||||
#include <Carbon/Carbon.h>
|
#include <Carbon/Carbon.h>
|
||||||
|
#include <CoreFoundation/CoreFoundation.h>
|
||||||
|
#include <IOKit/IOKitLib.h>
|
||||||
|
#include <IOKit/graphics/IOGraphicsLib.h>
|
||||||
#include <nall/macos/guard.hpp>
|
#include <nall/macos/guard.hpp>
|
||||||
#elif defined(DISPLAY_WINDOWS)
|
#elif defined(DISPLAY_WINDOWS)
|
||||||
#define far
|
#define far
|
||||||
|
|
|
@ -27,7 +27,7 @@ struct VideoDirectDraw : VideoDriver {
|
||||||
return initialize();
|
return initialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto setMonitor(bool monitor) -> bool override {
|
auto setMonitor(string monitor) -> bool override {
|
||||||
return initialize();
|
return initialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -276,9 +276,13 @@ static auto CALLBACK MonitorEnumProc(HMONITOR hMonitor, HDC hdcMonitor, LPRECT l
|
||||||
mi.cbSize = sizeof(MONITORINFOEX);
|
mi.cbSize = sizeof(MONITORINFOEX);
|
||||||
GetMonitorInfo(hMonitor, &mi);
|
GetMonitorInfo(hMonitor, &mi);
|
||||||
Video::Monitor monitor;
|
Video::Monitor monitor;
|
||||||
monitor.name = {"Monitor ", 1 + index};
|
string deviceName = (const char*)utf8_t(mi.szDevice);
|
||||||
string displayName = (const char*)utf8_t(mi.szDevice);
|
if(deviceName.beginsWith(R"(\\.\DISPLAYV)")) return true; //ignore pseudo-monitors
|
||||||
if(displayName.beginsWith(R"(\\.\DISPLAYV)")) return true; //ignore pseudo-monitors
|
DISPLAY_DEVICE dd{};
|
||||||
|
dd.cb = sizeof(DISPLAY_DEVICE);
|
||||||
|
EnumDisplayDevices(mi.szDevice, 0, &dd, 0);
|
||||||
|
string displayName = (const char*)utf8_t(dd.DeviceString);
|
||||||
|
monitor.name = {1 + monitors.size(), ": ", displayName};
|
||||||
monitor.primary = mi.dwFlags & MONITORINFOF_PRIMARY;
|
monitor.primary = mi.dwFlags & MONITORINFOF_PRIMARY;
|
||||||
monitor.x = lprcMonitor->left;
|
monitor.x = lprcMonitor->left;
|
||||||
monitor.y = lprcMonitor->top;
|
monitor.y = lprcMonitor->top;
|
||||||
|
@ -299,19 +303,44 @@ auto Video::hasMonitors() -> vector<Monitor> {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(DISPLAY_QUARTZ)
|
#if defined(DISPLAY_QUARTZ)
|
||||||
|
static auto MonitorKeyArrayCallback(const void* key, const void* value, void* context) -> void {
|
||||||
|
CFArrayAppendValue((CFMutableArrayRef)context, key);
|
||||||
|
}
|
||||||
|
|
||||||
auto Video::hasMonitors() -> vector<Monitor> {
|
auto Video::hasMonitors() -> vector<Monitor> {
|
||||||
vector<Monitor> monitors;
|
vector<Monitor> monitors;
|
||||||
@autoreleasepool {
|
@autoreleasepool {
|
||||||
uint count = [[NSScreen screens] count];
|
uint count = [[NSScreen screens] count];
|
||||||
for(uint index : range(count)) {
|
for(uint index : range(count)) {
|
||||||
NSRect rectangle = [[[NSScreen screens] objectAtindex:index] frame];
|
auto screen = [[NSScreen screens] objectAtIndex:index];
|
||||||
|
auto rectangle = [screen frame];
|
||||||
Monitor monitor;
|
Monitor monitor;
|
||||||
monitor.name = {"Monitor ", 1 + index}; //todo: retrieve vendor name here?
|
monitor.name = {1 + monitors.size(), ": Monitor"}; //fallback in case name lookup fails
|
||||||
monitor.primary = monitors.size() == 0; //on macOS, the primary monitor is always the first monitor.
|
monitor.primary = monitors.size() == 0; //on macOS, the primary monitor is always the first monitor.
|
||||||
monitor.x = rectangle.origin.x;
|
monitor.x = rectangle.origin.x;
|
||||||
monitor.y = rectangle.origin.y;
|
monitor.y = rectangle.origin.y;
|
||||||
monitor.width = rectangle.size.width;
|
monitor.width = rectangle.size.width;
|
||||||
monitor.height = rectangle.size.height;
|
monitor.height = rectangle.size.height;
|
||||||
|
//getting the name of the monitor on macOS: "Think Different"
|
||||||
|
auto screenDictionary = [screen deviceDescription];
|
||||||
|
auto screenID = [screenDictionary objectForKey:@"NSScreenNumber"];
|
||||||
|
auto displayID = [screenID unsignedIntValue];
|
||||||
|
auto displayPort = CGDisplayIOServicePort(displayID);
|
||||||
|
auto dictionary = IODisplayCreateInfoDictionary(displayPort, 0);
|
||||||
|
if(auto names = CFDictionaryGetValue(dictionary, CFSTR(kDisplayProductName))) {
|
||||||
|
auto languageKeys = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
|
||||||
|
CFDictionaryApplyFunction(names, MonitorKeyArrayCallback, (void*)languageKeys);
|
||||||
|
auto orderLanguageKeys = CFBundleCopyPreferredLocalizationsFromArray(languageKeys);
|
||||||
|
CFRelease(languageKeys);
|
||||||
|
if(orderLanguageKeys && CFArrayGetCount(orderLanguageKeys)) {
|
||||||
|
auto languageKey = CFArrayGetValueAtIndex(orderLanguageKeys, 0);
|
||||||
|
auto localName = CFDictionaryGetValue(names, languageKey);
|
||||||
|
monitor.name = {1 + monitors.size(), ": ", [(__bridge NSString*)localName UTF8String]};
|
||||||
|
CFRelease(localName);
|
||||||
|
}
|
||||||
|
CFRelease(orderLanguageKeys);
|
||||||
|
}
|
||||||
|
CFRelease(dictionary);
|
||||||
monitors.append(monitor);
|
monitors.append(monitor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -336,13 +365,36 @@ auto Video::hasMonitors() -> vector<Monitor> {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
auto crtc = XRRGetCrtcInfo(display, resources, output->crtc);
|
auto crtc = XRRGetCrtcInfo(display, resources, output->crtc);
|
||||||
monitor.name = output->name;
|
monitor.name = {1 + monitors.size(), ": ", output->name}; //fallback name
|
||||||
monitor.primary = false;
|
monitor.primary = false;
|
||||||
for(uint n : range(crtc->noutput)) monitor.primary |= crtc->outputs[n] == primary;
|
for(uint n : range(crtc->noutput)) monitor.primary |= crtc->outputs[n] == primary;
|
||||||
monitor.x = crtc->x;
|
monitor.x = crtc->x;
|
||||||
monitor.y = crtc->y;
|
monitor.y = crtc->y;
|
||||||
monitor.width = crtc->width;
|
monitor.width = crtc->width;
|
||||||
monitor.height = crtc->height;
|
monitor.height = crtc->height;
|
||||||
|
//Linux: "Think Low-Level"
|
||||||
|
Atom actualType;
|
||||||
|
int actualFormat;
|
||||||
|
unsigned long size;
|
||||||
|
unsigned long bytesAfter;
|
||||||
|
unsigned char* data = nullptr;
|
||||||
|
auto property = XRRGetOutputProperty(
|
||||||
|
display, resources->outputs[index],
|
||||||
|
XInternAtom(display, "EDID", 1), 0, 384,
|
||||||
|
0, 0, 0, &actualType, &actualFormat, &size, &bytesAfter, &data
|
||||||
|
);
|
||||||
|
if(size >= 128) {
|
||||||
|
string name{" "};
|
||||||
|
//there are four lights! er ... descriptors. one of them is the monitor name.
|
||||||
|
if(data[0x39] == 0xfc) memory::copy(name.get(), &data[0x3b], 13);
|
||||||
|
if(data[0x4b] == 0xfc) memory::copy(name.get(), &data[0x4d], 13);
|
||||||
|
if(data[0x5d] == 0xfc) memory::copy(name.get(), &data[0x5f], 13);
|
||||||
|
if(data[0x6f] == 0xfc) memory::copy(name.get(), &data[0x71], 13);
|
||||||
|
if(name.strip()) { //format: "name\n " -> "name"
|
||||||
|
monitor.name = {1 + monitors.size(), ": ", name, " [", output->name, "]"};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
XFree(data);
|
||||||
monitors.append(monitor);
|
monitors.append(monitor);
|
||||||
XRRFreeCrtcInfo(crtc);
|
XRRFreeCrtcInfo(crtc);
|
||||||
XRRFreeOutputInfo(output);
|
XRRFreeOutputInfo(output);
|
||||||
|
|
Loading…
Reference in New Issue