diff --git a/bsnes/emulator/emulator.hpp b/bsnes/emulator/emulator.hpp index bd40feaa..9f51bdc1 100644 --- a/bsnes/emulator/emulator.hpp +++ b/bsnes/emulator/emulator.hpp @@ -29,7 +29,7 @@ using namespace nall; namespace Emulator { 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 License = "GPLv3"; static const string Website = "https://byuu.org"; diff --git a/hiro/cocoa/widget/line-edit.cpp b/hiro/cocoa/widget/line-edit.cpp index 145bdfea..fb6f3c61 100755 --- a/hiro/cocoa/widget/line-edit.cpp +++ b/hiro/cocoa/widget/line-edit.cpp @@ -21,6 +21,7 @@ } -(IBAction) activate:(id)sender { + lineEdit->state.text = [[self stringValue] UTF8String]; lineEdit->doActivate(); } diff --git a/hiro/cocoa/widget/radio-label.cpp b/hiro/cocoa/widget/radio-label.cpp index aa5dd94a..3aa76dc6 100755 --- a/hiro/cocoa/widget/radio-label.cpp +++ b/hiro/cocoa/widget/radio-label.cpp @@ -27,7 +27,7 @@ auto pRadioLabel::construct() -> void { cocoaView = cocoaRadioLabel = [[CocoaRadioLabel alloc] initWith:self()]; pWidget::construct(); - if(state().checked) setChecked(); + setGroup(state().group); setText(state().text); } } @@ -41,24 +41,11 @@ auto pRadioLabel::destruct() -> void { auto pRadioLabel::minimumSize() const -> Size { 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 { - @autoreleasepool { - 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(self)) { - auto state = this == p ? NSOnState : NSOffState; - [p->cocoaView setState:state]; - } - } - } - } - } - } + setGroup(state().group); } auto pRadioLabel::setGeometry(Geometry geometry) -> void { @@ -66,9 +53,26 @@ auto pRadioLabel::setGeometry(Geometry geometry) -> void { geometry.x() - 1, geometry.y(), 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 { + @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(self)) { + auto state = p->state().checked ? NSOnState : NSOffState; + [p->cocoaView setState:state]; + } + } + } + } + } } auto pRadioLabel::setText(const string& text) -> void { diff --git a/hiro/cocoa/widget/table-view-item.cpp b/hiro/cocoa/widget/table-view-item.cpp index d4191745..d1fa48ea 100644 --- a/hiro/cocoa/widget/table-view-item.cpp +++ b/hiro/cocoa/widget/table-view-item.cpp @@ -37,6 +37,17 @@ auto pTableViewItem::setForegroundColor(Color color) -> 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 { diff --git a/hiro/cocoa/widget/table-view.cpp b/hiro/cocoa/widget/table-view.cpp index ebaca26a..2c81ee87 100755 --- a/hiro/cocoa/widget/table-view.cpp +++ b/hiro/cocoa/widget/table-view.cpp @@ -104,6 +104,7 @@ } -(void) tableViewSelectionDidChange:(NSNotification*)notification { + if(tableView->self()->locked()) return; for(auto& tableViewItem : tableView->state.items) { tableViewItem->state.selected = tableViewItem->offset() == [content selectedRow]; } diff --git a/hiro/cocoa/widget/text-edit.cpp b/hiro/cocoa/widget/text-edit.cpp index 2586c282..f478db87 100755 --- a/hiro/cocoa/widget/text-edit.cpp +++ b/hiro/cocoa/widget/text-edit.cpp @@ -87,6 +87,11 @@ auto pTextEdit::setFont(const Font& font) -> 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 { @autoreleasepool { [[cocoaView content] setString:[NSString stringWithUTF8String:text]]; diff --git a/hiro/cocoa/widget/text-edit.hpp b/hiro/cocoa/widget/text-edit.hpp index 4a876433..d0d112c2 100755 --- a/hiro/cocoa/widget/text-edit.hpp +++ b/hiro/cocoa/widget/text-edit.hpp @@ -21,6 +21,7 @@ struct pTextEdit : pWidget { auto setEnabled(bool enabled) -> void override; auto setFont(const Font& font) -> void override; auto setForegroundColor(Color color) -> void; + auto setGeometry(Geometry geometry) -> void override; auto setText(const string& text) -> void; auto setTextCursor(TextCursor textCursor) -> void; auto setWordWrap(bool wordWrap) -> void; diff --git a/hiro/core/widget/table-view-item.cpp b/hiro/core/widget/table-view-item.cpp index bc01adc6..9d55e0b4 100755 --- a/hiro/core/widget/table-view-item.cpp +++ b/hiro/core/widget/table-view-item.cpp @@ -100,6 +100,12 @@ auto mTableViewItem::setParent(mObject* parent, signed offset) -> 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; signal(setSelected, selected); return *this; diff --git a/ruby/GNUmakefile b/ruby/GNUmakefile index 41ac03ae..9700e14e 100755 --- a/ruby/GNUmakefile +++ b/ruby/GNUmakefile @@ -54,6 +54,7 @@ ifeq ($(platform),windows) endif ifeq ($(platform),macos) + ruby.options += -framework IOKit ruby.options += $(if $(findstring audio.openal,$(ruby)),-framework OpenAL) endif diff --git a/ruby/ruby.cpp b/ruby/ruby.cpp index cc7ec772..b9afe7f0 100755 --- a/ruby/ruby.cpp +++ b/ruby/ruby.cpp @@ -16,6 +16,9 @@ using namespace ruby; #include #include #include + #include + #include + #include #include #elif defined(DISPLAY_WINDOWS) #define far diff --git a/ruby/video/directdraw.cpp b/ruby/video/directdraw.cpp index 131ebc5d..fdd73c72 100755 --- a/ruby/video/directdraw.cpp +++ b/ruby/video/directdraw.cpp @@ -27,7 +27,7 @@ struct VideoDirectDraw : VideoDriver { return initialize(); } - auto setMonitor(bool monitor) -> bool override { + auto setMonitor(string monitor) -> bool override { return initialize(); } diff --git a/ruby/video/video.cpp b/ruby/video/video.cpp index 9be84a62..f419b962 100644 --- a/ruby/video/video.cpp +++ b/ruby/video/video.cpp @@ -276,9 +276,13 @@ static auto CALLBACK MonitorEnumProc(HMONITOR hMonitor, HDC hdcMonitor, LPRECT l mi.cbSize = sizeof(MONITORINFOEX); GetMonitorInfo(hMonitor, &mi); Video::Monitor monitor; - monitor.name = {"Monitor ", 1 + index}; - string displayName = (const char*)utf8_t(mi.szDevice); - if(displayName.beginsWith(R"(\\.\DISPLAYV)")) return true; //ignore pseudo-monitors + string deviceName = (const char*)utf8_t(mi.szDevice); + if(deviceName.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.x = lprcMonitor->left; monitor.y = lprcMonitor->top; @@ -299,19 +303,44 @@ auto Video::hasMonitors() -> vector { #endif #if defined(DISPLAY_QUARTZ) +static auto MonitorKeyArrayCallback(const void* key, const void* value, void* context) -> void { + CFArrayAppendValue((CFMutableArrayRef)context, key); +} + auto Video::hasMonitors() -> vector { vector monitors; @autoreleasepool { uint count = [[NSScreen screens] 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.name = {"Monitor ", 1 + index}; //todo: retrieve vendor name here? - monitor.primary = monitors.size() == 0; //on macOS, the primary monitor is always the first monitor. + 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.x = rectangle.origin.x; monitor.y = rectangle.origin.y; monitor.width = rectangle.size.width; 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); } } @@ -336,13 +365,36 @@ auto Video::hasMonitors() -> vector { continue; } auto crtc = XRRGetCrtcInfo(display, resources, output->crtc); - monitor.name = output->name; + monitor.name = {1 + monitors.size(), ": ", output->name}; //fallback name monitor.primary = false; for(uint n : range(crtc->noutput)) monitor.primary |= crtc->outputs[n] == primary; monitor.x = crtc->x; monitor.y = crtc->y; monitor.width = crtc->width; 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); XRRFreeCrtcInfo(crtc); XRRFreeOutputInfo(output);