head 1.1; access; symbols pkgsrc-2026Q1:1.1.0.44 pkgsrc-2026Q1-base:1.1 pkgsrc-2025Q4:1.1.0.42 pkgsrc-2025Q4-base:1.1 pkgsrc-2025Q3:1.1.0.40 pkgsrc-2025Q3-base:1.1 pkgsrc-2025Q2:1.1.0.38 pkgsrc-2025Q2-base:1.1 pkgsrc-2025Q1:1.1.0.36 pkgsrc-2025Q1-base:1.1 pkgsrc-2024Q4:1.1.0.34 pkgsrc-2024Q4-base:1.1 pkgsrc-2024Q3:1.1.0.32 pkgsrc-2024Q3-base:1.1 pkgsrc-2024Q2:1.1.0.30 pkgsrc-2024Q2-base:1.1 pkgsrc-2024Q1:1.1.0.28 pkgsrc-2024Q1-base:1.1 pkgsrc-2023Q4:1.1.0.26 pkgsrc-2023Q4-base:1.1 pkgsrc-2023Q3:1.1.0.24 pkgsrc-2023Q3-base:1.1 pkgsrc-2023Q2:1.1.0.22 pkgsrc-2023Q2-base:1.1 pkgsrc-2023Q1:1.1.0.20 pkgsrc-2023Q1-base:1.1 pkgsrc-2022Q4:1.1.0.18 pkgsrc-2022Q4-base:1.1 pkgsrc-2022Q3:1.1.0.16 pkgsrc-2022Q3-base:1.1 pkgsrc-2022Q2:1.1.0.14 pkgsrc-2022Q2-base:1.1 pkgsrc-2022Q1:1.1.0.12 pkgsrc-2022Q1-base:1.1 pkgsrc-2021Q4:1.1.0.10 pkgsrc-2021Q4-base:1.1 pkgsrc-2021Q3:1.1.0.8 pkgsrc-2021Q3-base:1.1 pkgsrc-2021Q2:1.1.0.6 pkgsrc-2021Q2-base:1.1 pkgsrc-2021Q1:1.1.0.4 pkgsrc-2021Q1-base:1.1 pkgsrc-2020Q4:1.1.0.2 pkgsrc-2020Q4-base:1.1; locks; strict; comment @# @; 1.1 date 2020.11.01.20.18.30; author js; state Exp; branches; next ; commitid s5cbBynORqqIVduC; desc @@ 1.1 log @emulators/rpcemu: Update to a newer patchset for macOS support @ text @From http://www.riscos.info/pipermail/rpcemu/2020-May/002887.html --- original/ArmDynarec.c 2020-05-06 20:19:23.000000000 +0100 +++ src/ArmDynarec.c 2020-05-07 21:14:11.000000000 +0100 @@@@ -580,12 +580,39 @@@@ { const long page_size = sysconf(_SC_PAGESIZE); const long page_mask = ~(page_size - 1); - void *start; + void *start, *addr; long end; + int mmap_flags = 0; start = (void *) ((long) ptr & page_mask); end = ((long) ptr + len + page_size - 1) & page_mask; len = (size_t) (end - (long) start); + +#if __APPLE__ + // More up-to-date versions of OS X require "mmap" to be called prior to "mprotect". + // Certain versions also require the MAP_JIT flag as well. + // Try without first, and if that fails, add the flag in. + mmap_flags = MAP_PRIVATE | MAP_ANON | MAP_FIXED; + + addr = mmap(NULL, page_size, PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANON, -1, 0); + if (addr == MAP_FAILED) + { + mmap_flags |= MAP_JIT; + } + else + { + munmap(addr, page_size); + } + + addr = mmap(start, len, PROT_READ | PROT_WRITE | PROT_EXEC, mmap_flags, -1, 0); + + if (addr == MAP_FAILED) + { + perror("mmap"); + exit(1); + } + +#endif if (mprotect(start, len, PROT_READ | PROT_WRITE | PROT_EXEC) != 0) { perror("mprotect"); --- /dev/null 2020-05-14 17:35:51.000000000 +0100 +++ src/qt5/choose_dialog.cpp 2020-05-07 21:05:20.000000000 +0100 @@@@ -0,0 +1,107 @@@@ +/* + RPCEmu - An Acorn system emulator + + Copyright (C) 2016-2017 Matthew Howkins + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include "choose_dialog.h" +#include "preferences-macosx.h" + +ChooseDialog::ChooseDialog(QWidget *parent) : QDialog(parent) +{ + setWindowTitle("RPCEmu - Choose Data Directory"); + + buttons_box = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + + // Create preamble label. + QString str = QString("

Before using RPCEmu for the first time, you must select the directory
" + "that contains the folders and files required by the emulator, such as
" + "ROMs, hard drive images and the HostFS share.

" + "

You can show this dialogue again by holding down the Command key
" + "whilst the application is loading.

"); + + preamble_label = new QLabel(str); + + // Create choose label. + choose_label = new QLabel(); + choose_label->setText("Please choose a directory below:"); + + // Create directory line edit. + directory_edit = new QLineEdit(); + directory_edit->setMaxLength(511); + directory_edit->setReadOnly(true); + + // Create directory button. + directory_button = new QPushButton("Select...", this); + + // Create box for line edit and button. + directory_hbox = new QHBoxLayout(); + directory_hbox->setSpacing(16); + directory_hbox->addWidget(directory_edit); + directory_hbox->addWidget(directory_button); + + grid = new QGridLayout(this); + grid->addWidget(preamble_label, 0, 0); + grid->addWidget(choose_label, 1, 0); + grid->addLayout(directory_hbox, 2, 0); + grid->addWidget(buttons_box, 3, 0); + + // Connect actions to widgets. + connect(directory_button, &QPushButton::pressed, this, &ChooseDialog::directory_button_pressed); + + connect(buttons_box, &QDialogButtonBox::accepted, this, &QDialog::accept); + connect(buttons_box, &QDialogButtonBox::rejected, this, &QDialog::reject); + + connect(this, &QDialog::accepted, this, &ChooseDialog::dialog_accepted); + connect(this, &QDialog::accepted, this, &ChooseDialog::dialog_rejected); + + this->setFixedSize(this->sizeHint()); +} + +ChooseDialog::~ChooseDialog() +{ +} + +void ChooseDialog::directory_button_pressed() +{ + QFileDialog folderDialog; + folderDialog.setWindowTitle("Choose Data Directory"); + folderDialog.setFileMode(QFileDialog::Directory); + + if (folderDialog.exec()) + { + QStringList selection = folderDialog.selectedFiles(); + QString folderName = selection.at(0); + + directory_edit->setText(folderName); + } +} + +void ChooseDialog::dialog_accepted() +{ + QString selectedFolder = directory_edit->text(); + QByteArray ba = selectedFolder.toUtf8(); + + char *ptr = ba.data(); + preferences_set_data_directory(ptr); +} + +void ChooseDialog::dialog_rejected() +{ +} + --- /dev/null 2020-05-14 17:35:51.000000000 +0100 +++ src/qt5/choose_dialog.h 2020-05-07 21:05:20.000000000 +0100 @@@@ -0,0 +1,64 @@@@ +/* + RPCEmu - An Acorn system emulator + + Copyright (C) 2016-2017 Matthew Howkins + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef CHOOSE_DIALOG_H +#define CHOOSE_DIALOG_H + +#include +#include +#include +#include +#include +#include + +#include "rpc-qt5.h" +#include "rpcemu.h" + +class ChooseDialog : public QDialog +{ + + Q_OBJECT + +public: + ChooseDialog(QWidget *parent = 0); + virtual ~ChooseDialog(); + +private slots: + void directory_button_pressed(); + + void dialog_accepted(); + void dialog_rejected(); + +private: + + QLabel *preamble_label; + QLabel *choose_label; + + QHBoxLayout *directory_hbox; + QLineEdit *directory_edit; + QPushButton *directory_button; + + QDialogButtonBox *buttons_box; + + QGridLayout *grid; + +}; + +#endif --- /dev/null 2020-05-14 17:35:51.000000000 +0100 +++ src/macosx/events-macosx.h 2020-05-07 21:05:20.000000000 +0100 @@@@ -0,0 +1,45 @@@@ +/* + RPCEmu - An Acorn system emulator + + Copyright (C) 2017 Peter Howkins + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef __EVENTS_MACOSX_H__ +#define __EVENTS_MACOSX_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + nativeEventTypeModifiersChanged = 1 +} NativeEventType; + +typedef struct +{ + bool processed; + int eventType; + uint modifierMask; +} NativeEvent; + +extern NativeEvent* handle_native_event(void *message); + +#ifdef __cplusplus +} +#endif + +#endif // __EVENTS_MACOSX_H__ --- /dev/null 2020-05-14 17:35:51.000000000 +0100 +++ src/macosx/events-macosx.m 2020-05-07 21:05:20.000000000 +0100 @@@@ -0,0 +1,56 @@@@ +/* + RPCEmu - An Acorn system emulator + + Copyright (C) 2017 Matthew Howkins + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#define UNUSED(x) (void)(x) + +#include +#include + +#include +#include +#include + +#include "rpcemu.h" + +#include "events-macosx.h" + + +NativeEvent* handle_native_event(void *message) +{ + // This extracts information from the Cocoa event and passes it back up the chain to C++. + NSEvent *event = (NSEvent *) message; + + NativeEvent *result = (NativeEvent *) malloc(sizeof(NativeEvent)); + + // Only handle flags changed events, which are raised for modifier key changes. + if (event.type == NSEventTypeFlagsChanged) + { + result->eventType = nativeEventTypeModifiersChanged; + result->modifierMask = event.modifierFlags; + result->processed = 1; + } + else + { + result->processed = 0; + } + + // Return zero if the event is not handled here. + return result; +} --- /dev/null 2020-05-14 17:35:51.000000000 +0100 +++ src/macosx/hid-macosx.h 2020-05-07 21:05:20.000000000 +0100 @@@@ -0,0 +1,34 @@@@ +///* +// RPCEmu - An Acorn system emulator +// +// Copyright (C) 2017 Matthew Howkins +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +// */ + +#ifndef __HID_MACOSX_H__ +#define __HID_MACOSX_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +extern void init_hid_manager(void); + +#ifdef __cplusplus +} +#endif + +#endif // __HID_MACOSX_H__ --- /dev/null 2020-05-14 17:35:51.000000000 +0100 +++ src/macosx/hid-macosx.m 2020-05-07 21:05:20.000000000 +0100 @@@@ -0,0 +1,165 @@@@ +///* +// RPCEmu - An Acorn system emulator +// +// Copyright (C) 2017 Matthew Howkins +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +// */ + +#include +#include + +#include +#include +#include + +#include "keyboard.h" +#include "keyboard_macosx.h" + +#define UNUSED(x) (void)(x) + +static IOHIDManagerRef hidManager = NULL; + +CFDictionaryRef createHIDDeviceMatchingDictionary(uint32 usagePage, uint32 usage) +{ + CFMutableDictionaryRef dictionary = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + + if (dictionary) + { + CFNumberRef number = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &usagePage); + if (number) + { + CFDictionarySetValue(dictionary, CFSTR(kIOHIDDeviceUsagePageKey), number); + CFRelease(number); + + number = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &usage); + if (number) + { + CFDictionarySetValue(dictionary, CFSTR(kIOHIDDeviceUsageKey), number); + CFRelease(number); + + return dictionary; + } + } + + CFRelease(dictionary); + } + + return NULL; +} + +void processHIDCallback(void *context, IOReturn result, void *sender, IOHIDValueRef value) +{ + UNUSED(result); + UNUSED(sender); + + if (context != hidManager) return; + + IOHIDElementRef element = IOHIDValueGetElement(value); + if (IOHIDElementGetUsagePage(element) != kHIDPage_KeyboardOrKeypad || IOHIDElementGetUsage(element) != kHIDUsage_KeyboardCapsLock) return; + + CFIndex pressed = IOHIDValueGetIntegerValue(value); + + uint8 scanCodes[] = { 0x58 }; + + if (pressed == 0) + { + keyboard_key_release(scanCodes); + } + else + { + keyboard_key_press(scanCodes); + } +} + +const char *getCurrentKeyboardLayoutName() +{ + TISInputSourceRef currentSource = TISCopyCurrentKeyboardInputSource(); + NSString *inputSource = (__bridge NSString *)(TISGetInputSourceProperty(currentSource, kTISPropertyInputSourceID)); + NSUInteger lastIndex = [inputSource rangeOfString:@@"." options:NSBackwardsSearch].location; + + NSString *layoutName = [inputSource substringFromIndex: lastIndex + 1]; + lastIndex = [layoutName rangeOfString:@@" - "].location; + + if (lastIndex != NSNotFound) layoutName = [layoutName substringToIndex: lastIndex]; + + return [layoutName UTF8String]; +} + +void terminate_hid_manager(void) +{ + if (!hidManager) return; + + IOHIDManagerUnscheduleFromRunLoop(hidManager, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); + IOHIDManagerRegisterInputValueCallback(hidManager, NULL, NULL); + IOHIDManagerClose(hidManager, 0); + + CFRelease(hidManager); + + hidManager = NULL; +} + +void init_hid_manager(void) +{ + const char *layoutName = getCurrentKeyboardLayoutName(); + keyboard_configure_layout(layoutName); + + hidManager = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone); + if (!hidManager) return; + + CFDictionaryRef keyboard = NULL, keypad = NULL; + CFArrayRef matches = NULL; + + keyboard = createHIDDeviceMatchingDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_Keyboard); + if (!keyboard) + { + IOHIDManagerClose(hidManager, 0); + return; + } + + keypad = createHIDDeviceMatchingDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_Keypad); + if (!keypad) + { + CFRelease(keyboard); + IOHIDManagerClose(hidManager, 0); + + return; + } + + CFDictionaryRef matchesList[] = {keyboard, keypad}; + matches = CFArrayCreate(kCFAllocatorDefault, (const void**) matchesList, 2, NULL); + if (!matches) + { + CFRelease(keypad); + CFRelease(keyboard); + IOHIDManagerClose(hidManager, 0); + + return; + } + + IOHIDManagerSetDeviceMatchingMultiple(hidManager, matches); + IOHIDManagerRegisterInputValueCallback(hidManager, processHIDCallback, hidManager); + IOHIDManagerScheduleWithRunLoop(hidManager, CFRunLoopGetMain(), kCFRunLoopDefaultMode); + if (IOHIDManagerOpen(hidManager, kIOHIDOptionsTypeNone) != kIOReturnSuccess) + { + terminate_hid_manager(); + } + + CFRelease(matches); + CFRelease(keypad); + CFRelease(keyboard); +} + + --- /dev/null 2020-05-14 17:35:51.000000000 +0100 +++ src/hostfs-macosx.c 2020-05-07 21:05:20.000000000 +0100 @@@@ -0,0 +1,121 @@@@ +#include +#include +#include +#include + +#include +#include + +#include "hostfs_internal.h" + +/** + * Convert ADFS time-stamped Load-Exec addresses to the equivalent time_t. + * + * @@param load RISC OS load address (assumed to be time-stamped) + * @@param exec RISC OS exec address (assumed to be time-stamped) + * @@return Time converted to time_t format + * + * Code adapted from fs/adfs/inode.c from Linux licensed under GPL2. + * Copyright (C) 1997-1999 Russell King + */ +static time_t +hostfs_adfs2host_time(uint32_t load, uint32_t exec) +{ + uint32_t high = load << 24; + uint32_t low = exec; + + high |= low >> 8; + low &= 0xff; + + if (high < 0x3363996a) { + /* Too early */ + return 0; + } else if (high >= 0x656e9969) { + /* Too late */ + return 0x7ffffffd; + } + + high -= 0x336e996a; + return (((high % 100) << 8) + low) / 100 + (high / 100 << 8); +} + +/** + * Read information about an object. + * + * @@param host_pathname Full Host path to object + * @@param object_info Return object info (filled-in) + */ +void +hostfs_read_object_info_platform(const char *host_pathname, + risc_os_object_info *object_info) +{ + struct stat info; + uint32_t low, high; + + assert(host_pathname != NULL); + assert(object_info != NULL); + + // Ignore DS_Store files. + if (strcasestr(host_pathname, ".DS_Store") != NULL) + { + object_info->type = OBJECT_TYPE_NOT_FOUND; + return; + } + + if (stat(host_pathname, &info)) { + /* Error reading info about the object */ + switch (errno) { + case ENOENT: /* Object not found */ + case ENOTDIR: /* A path component is not a directory */ + object_info->type = OBJECT_TYPE_NOT_FOUND; + break; + + default: + /* Other error */ + fprintf(stderr, + "hostfs_read_object_info_platform() could not stat() \'%s\': %s %d\n", + host_pathname, strerror(errno), errno); + object_info->type = OBJECT_TYPE_NOT_FOUND; + break; + } + + return; + } + + /* We were able to read about the object */ + if (S_ISREG(info.st_mode)) { + object_info->type = OBJECT_TYPE_FILE; + } else if (S_ISDIR(info.st_mode)) { + object_info->type = OBJECT_TYPE_DIRECTORY; + } else { + /* Treat types other than file or directory as not found */ + object_info->type = OBJECT_TYPE_NOT_FOUND; + return; + } + + low = (uint32_t) ((info.st_mtime & 255) * 100); + high = (uint32_t) ((info.st_mtime / 256) * 100 + (low >> 8) + 0x336e996a); + + /* If the file has filetype and timestamp, additional values will need to be filled in later */ + object_info->load = (high >> 24); + object_info->exec = (low & 0xff) | (high << 8); + + object_info->length = info.st_size; +} + +/** + * Apply the timestamp to the supplied host object + * + * @@param host_path Full path to object (file or dir) in host format + * @@param load RISC OS load address (must contain time-stamp) + * @@param exec RISC OS exec address (must contain time-stamp) + */ +void +hostfs_object_set_timestamp_platform(const char *host_path, uint32_t load, uint32_t exec) +{ + struct utimbuf t; + + t.actime = t.modtime = hostfs_adfs2host_time(load, exec); + utime(host_path, &t); + /* TODO handle error in utime() */ +} --- original/hostfs.c 2020-05-06 20:19:23.000000000 +0100 +++ src/hostfs.c 2020-05-07 21:05:20.000000000 +0100 @@@@ -273,6 +273,9 @@@@ case '>': *host_path++ = '^'; break; + case (char) 160: + *host_path++ = ' '; + break; default: *host_path++ = *path; break; @@@@ -539,7 +542,7 @@@@ while ((entry = readdir(d)) != NULL) { char entry_path[PATH_MAX], ro_leaf[PATH_MAX]; - /* Ignore the current directory and it's parent */ + /* Ignore the current directory and its parent */ if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) { continue; } @@@@ -1650,7 +1653,7 @@@@ char entry_path[PATH_MAX], ro_leaf[PATH_MAX]; unsigned string_space; - /* Ignore the current directory and it's parent */ + /* Ignore the current directory and its parent */ if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) { continue; } --- original/iomd.c 2020-05-06 20:19:23.000000000 +0100 +++ src/iomd.c 2020-05-07 21:05:20.000000000 +0100 @@@@ -840,19 +840,28 @@@@ } /* Middle */ if (mouse_buttons & 4) { + +#ifdef __APPLE__ + temp |= 0x20; +#else if (config.mousetwobutton) { temp |= 0x10; // bit 4 } else { temp |= 0x20; // bit 5 } +#endif } /* Right */ if (mouse_buttons & 2) { +#ifdef __APPLE__ + temp |= 0x10; +#else if (config.mousetwobutton) { temp |= 0x20; // bit 5 } else { temp |= 0x10; // bit 4 } +#endif } /* bit 0 contains the monitor id bit, 0 for VGA, 1 for TV type monitors. --- original/keyboard.c 2020-05-06 20:19:23.000000000 +0100 +++ src/keyboard.c 2020-05-07 21:05:20.000000000 +0100 @@@@ -44,6 +44,10 @@@@ #include "arm.h" #include "i8042.h" +#ifdef __APPLE__ +#include "keyboard_macosx.h" +#endif + /* Keyboard Commands */ #define KBD_CMD_ENABLE 0xf4 #define KBD_CMD_RESET 0xff @@@@ -254,6 +258,10 @@@@ /* Mousehack reset */ mouse_hack.pointer = 0; mouse_hack.cursor_linked = 1; + +#ifdef __APPLE__ + keyboard_reset_modifiers(0); +#endif } static uint8_t @@@@ -731,6 +739,7 @@@@ { uint8_t tmp; +#ifndef __APPLE__ if (config.mousetwobutton) { /* To help people with only two buttons on their mouse, swap the behaviour of middle and right buttons */ @@@@ -740,6 +749,7 @@@@ mouseb = mousel | (mousem << 1) | (mouser << 2); } +#endif tmp = (mouseb & 7) | 8; @@@@ -1192,6 +1202,15 @@@@ if (mouse.buttons & 1) { buttons |= 4; /* Left button */ } + +#ifdef __APPLE__ + if (mouse.buttons & 2) { + buttons |= 1; /* Right button */ + } + if (mouse.buttons & 4) { + buttons |= 2; /* Middle button */ + } +#else if (config.mousetwobutton) { /* To help people with only two buttons on their mouse, swap the behaviour of middle and right buttons */ @@@@ -1209,6 +1228,8 @@@@ buttons |= 2; /* Middle button */ } } +#endif + arm.reg[2] = buttons; arm.reg[3] = 0; /* R3 = time of button change */ @@@@ -1258,4 +1279,4 @@@@ mouse_osunits_to_host(osx, osy, &x, &y); rpcemu_move_host_mouse(x, y); -} \ No newline at end of file +} --- /dev/null 2020-05-14 17:35:51.000000000 +0100 +++ src/qt5/keyboard_macosx.c 2020-05-07 21:05:20.000000000 +0100 @@@@ -0,0 +1,351 @@@@ +/* + RPCEmu - An Acorn system emulator + + Copyright (C) 2017 Matthew Howkins + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "rpcemu.h" +#include "keyboard.h" + +#include + +#define UNUSED(x) (void)(x) + +const int MAX_KEYBOARD_LAYOUTS = 20; + +typedef enum +{ + keyboardLayoutUndefined = 0, + keyboardLayoutBritish = 1, + keyboardLayoutFrench = 2 +} KeyboardLayoutType; + +static int keyboardType; + +typedef struct { + uint32_t virtual_key[MAX_KEYBOARD_LAYOUTS]; // Cocoa virtual keys + uint8_t set_2[8]; // PS/2 Set 2 make code +} KeyMapInfo; + +// Mac virtual keys can be found in the following file: +// +// /System/Library/Frameworks/Carbon.framework/Versions/A/Frameworks/HIToolbox.framework/Versions/A/Headers/Events.h. +// +// Key mappings are defined as follows: +// +// The first member is an array of virtual key codes. There will be at least three elements in the array for each key. +// +// The first element indicates whether or not there are different mappings for different keyboard layouts for this key code. +// If the value is 0, each keyboard layout uses the same mapping. Where the value is 1, there are different mappings for different layouts. +// An example of the former is "0" and of the latter, "Z" (in French, this is "Y"). +// +// The second element in the array is the virtual key to use for the default language, British. +// If additional, non-British languages are defined in the 'KeyboardLayoutType' enumeration (above) and in the +// 'configureKeyboardLayout' function (below), virtual keys for these languages can be specified. +// For example, on a French keyboard, 'Y' and 'Z' are transposed. Therefore, for each of the mappings for these keys, +// two virtual keys are listed. +// +// The list of virtual key codes must be terminated with an 0xFFFF element. +// +// The second member is an array of PS/2 set 2 codes. + +static const KeyMapInfo key_map[] = { + { { 0, kVK_Escape, 0xFFFF }, { 0x76 } }, // Escape + + { { 0, kVK_ISO_Section, 0xFFFF }, { 0x0e } }, // ` + { { 0, kVK_ANSI_1, 0xFFFF}, { 0x16 } }, // 1 + { { 0, kVK_ANSI_2, 0xFFFF }, { 0x1e } }, // 2 + { { 0, kVK_ANSI_3, 0xFFFF}, { 0x26 } }, // 3 + { { 0, kVK_ANSI_4, 0xFFFF }, { 0x25 } }, // 4 + { { 0, kVK_ANSI_5, 0xFFFF }, { 0x2e } }, // 5 + { { 0, kVK_ANSI_6, 0xFFFF }, { 0x36 } }, // 6 + { { 0, kVK_ANSI_7, 0xFFFF }, { 0x3d } }, // 7 + { { 0, kVK_ANSI_8, 0xFFFF }, { 0x3e } }, // 8 + { { 0, kVK_ANSI_9, 0xFFFF }, { 0x46 } }, // 9 + { { 0, kVK_ANSI_0, 0xFFFF }, { 0x45 } }, // 0 + { { 0, kVK_ANSI_Minus, 0xFFFF }, { 0x4e } }, // - + { { 0, kVK_ANSI_Equal, 0xFFFF }, { 0x55 } }, // = + { { 0, kVK_Delete, 0xFFFF }, { 0x66 } }, // Backspace + + { { 0, kVK_Tab, 0xFFFF }, { 0x0d } }, // Tab + { { 0, kVK_ANSI_Q, 0xFFFF }, { 0x15 } }, // Q + { { 0, kVK_ANSI_W, 0xFFFF }, { 0x1d } }, // W + { { 0, kVK_ANSI_E, 0xFFFF }, { 0x24 } }, // E + { { 0, kVK_ANSI_R, 0xFFFF }, { 0x2d } }, // R + { { 0, kVK_ANSI_T, 0xFFFF}, { 0x2c } }, // T + { { 1, kVK_ANSI_Y, kVK_ANSI_Z, 0xFFFF }, { 0x35 } }, // Y + { { 0, kVK_ANSI_U, 0xFFFF }, { 0x3c } }, // U + { { 0, kVK_ANSI_I, 0xFFFF }, { 0x43 } }, // I + { { 0, kVK_ANSI_O, 0xFFFF }, { 0x44 } }, // O + { { 0, kVK_ANSI_P, 0xFFFF }, { 0x4d } }, // P + { { 0, kVK_ANSI_LeftBracket, 0xFFFF }, { 0x54 } }, // [ + { { 0, kVK_ANSI_RightBracket, 0xFFFF }, { 0x5b } }, // ] + { { 0, kVK_Return, 0xFFFF }, { 0x5a } }, // Return + + { { 0, kVK_Control, 0xFFFF }, { 0x14 } }, // Left Ctrl + { { 0, kVK_ANSI_A, 0xFFFF }, { 0x1c } }, // A + { { 0, kVK_ANSI_S, 0xFFFF }, { 0x1b } }, // S + { { 0, kVK_ANSI_D, 0xFFFF }, { 0x23 } }, // D + { { 0, kVK_ANSI_F, 0xFFFF }, { 0x2b } }, // F + { { 0, kVK_ANSI_G, 0xFFFF }, { 0x34 } }, // G + { { 1, kVK_ANSI_H, 0xFFFF }, { 0x33 } }, // H + { { 0, kVK_ANSI_J, 0xFFFF }, { 0x3b } }, // J + { { 0, kVK_ANSI_K, 0xFFFF }, { 0x42 } }, // K + { { 0, kVK_ANSI_L, 0xFFFF }, { 0x4b } }, // L + { { 0, kVK_ANSI_Semicolon, 0xFFFF }, { 0x4c } }, // ; + { { 0, kVK_ANSI_Quote, 0xFFFF }, { 0x52 } }, // ' + { { 0, kVK_ANSI_Backslash, 0xFFFF }, { 0x5d } }, // # (International only) + + { { 0, kVK_ANSI_Grave, 0xFFFF }, { 0x61 } }, // ` + { { 1, kVK_ANSI_Z, kVK_ANSI_Y, 0xFFFF }, { 0x1a } }, // Z + { { 0, kVK_ANSI_X, 0xFFFF }, { 0x22 } }, // X + { { 0, kVK_ANSI_C, 0xFFFF }, { 0x21 } }, // C + { { 0, kVK_ANSI_V, 0xFFFF }, { 0x2a } }, // V + { { 0, kVK_ANSI_B, 0xFFFF }, { 0x32 } }, // B + { { 0, kVK_ANSI_N, 0xFFFF }, { 0x31 } }, // N + { { 0, kVK_ANSI_M, 0xFFFF }, { 0x3a } }, // M + { { 0, kVK_ANSI_Comma, 0xFFFF }, { 0x41 } }, // , + { { 0, kVK_ANSI_Period, 0xFFFF }, { 0x49 } }, // . + { { 0, kVK_ANSI_Slash, 0xFFFF }, { 0x4a } }, // / + + { { 0, kVK_Space, 0xFFFF }, { 0x29 } }, // Space + + { { 0, kVK_F1, 0xFFFF }, { 0x05 } }, // F1 + { { 0, kVK_F2, 0xFFFF }, { 0x06 } }, // F2 + { { 0, kVK_F3, 0xFFFF }, { 0x04 } }, // F3 + { { 0, kVK_F4, 0xFFFF }, { 0x0c } }, // F4 + { { 0, kVK_F5, 0xFFFF }, { 0x03 } }, // F5 + { { 0, kVK_F6, 0xFFFF }, { 0x0b } }, // F6 + { { 0, kVK_F7, 0xFFFF }, { 0x83 } }, // F7 + { { 0, kVK_F8, 0xFFFF }, { 0x0a } }, // F8 + { { 0, kVK_F9, 0xFFFF }, { 0x01 } }, // F9 + { { 0, kVK_F10, 0xFFFF }, { 0x09 } }, // F10 + { { 0, kVK_F11, 0xFFFF }, { 0x78 } }, // F11 + { { 0, kVK_F12, 0xFFFF }, { 0x07 } }, // F12 + + { { 0, kVK_F13, 0xFFFF }, { 0xe0, 0x7c } }, // Print Screen/SysRq + { { 0, kVK_F14, 0xFFFF }, { 0x7e } }, // Scroll Lock + { { 0, kVK_F15, 0xFFFF }, { 0xe1, 0x14, 0x77, 0xe1, 0xf0, 0x14, 0xf0, 0x77 } }, // Break + + { { 0, kVK_ANSI_KeypadClear, 0xFFFF }, { 0x77 } }, // Keypad Num Lock + { { 0, kVK_ANSI_KeypadDivide, 0xFFFF }, { 0xe0, 0x4a } }, // Keypad / + { { 0, kVK_ANSI_KeypadMultiply, 0xFFFF }, { 0x7c } }, // Keypad * + { { 0, kVK_ANSI_Keypad7, 0xFFFF }, { 0x6c } }, // Keypad 7 + { { 0, kVK_ANSI_Keypad8, 0xFFFF }, { 0x75 } }, // Keypad 8 + { { 0, kVK_ANSI_Keypad9, 0xFFFF }, { 0x7d } }, // Keypad 9 + { { 0, kVK_ANSI_KeypadMinus, 0xFFFF }, { 0x7b } }, // Keypad - + { { 0, kVK_ANSI_Keypad4, 0xFFFF }, { 0x6b } }, // Keypad 4 + { { 0, kVK_ANSI_Keypad5, 0xFFFF }, { 0x73 } }, // Keypad 5 + { { 0, kVK_ANSI_Keypad6, 0xFFFF }, { 0x74 } }, // Keypad 6 + { { 0, kVK_ANSI_KeypadPlus, 0xFFFF }, { 0x79 } }, // Keypad + + { { 0, kVK_ANSI_Keypad1, 0xFFFF }, { 0x69 } }, // Keypad 1 + { { 0, kVK_ANSI_Keypad2, 0xFFFF }, { 0x72 } }, // Keypad 2 + { { 0, kVK_ANSI_Keypad3, 0xFFFF }, { 0x7a } }, // Keypad 3 + { { 0, kVK_ANSI_Keypad0, 0xFFFF }, { 0x70 } }, // Keypad 0 + { { 0, kVK_ANSI_KeypadDecimal, 0xFFFF }, { 0x71 } }, // Keypad . + { { 0, kVK_ANSI_KeypadEnter, 0xFFFF }, { 0xe0, 0x5a } }, // Keypad Enter + + { { 0, kVK_Function, 0xFFFF }, { 0xe0, 0x70 } }, // Insert + { { 0, kVK_ForwardDelete, 0xFFFF }, { 0xe0, 0x71 } }, // Delete + { { 0, kVK_Home, 0xFFFF }, { 0xe0, 0x6c } }, // Home + { { 0, kVK_End, 0xFFFF }, { 0xe0, 0x69 } }, // End + { { 0, kVK_UpArrow, 0xFFFF }, { 0xe0, 0x75 } }, // Up + { { 0, kVK_DownArrow, 0xFFFF }, { 0xe0, 0x72 } }, // Down + { { 0, kVK_LeftArrow, 0xFFFF }, { 0xe0, 0x6b } }, // Left + { { 0, kVK_RightArrow, 0xFFFF }, { 0xe0, 0x74 } }, // Right + { { 0, kVK_PageUp, 0xFFFF }, { 0xe0, 0x7d } }, // Page Up + { { 0, kVK_PageDown, 0xFFFF }, { 0xe0, 0x7a } }, // Page Down + + { { 0, kVK_F16, 0xFFFF }, { 0xe0, 0x2f } }, // Application (Win Menu) + + { { 0xFFFF }, { 0, 0 } }, +}; + +typedef enum +{ + modifierKeyStateShift = 0, + modifierKeyStateControl = 1, + modifierKeyStateAlt = 2, + modifierKeyStateCapsLock = 3, + modifierKeyStateCommand = 4 +} ModifierKeyCode; + +typedef struct +{ + int keyState[5]; +} ModifierState; + +ModifierState modifierState; + +typedef struct { + uint32_t modifierMask; + int checkMask; + uint maskLeft; + uint maskRight; + uint8_t set_2_left[8]; + uint8_t set_2_right[8]; + int simulateMenuButton; +} ModifierMapInfo; + +// The following are from the "NSEventModifierFlagOption" enumeration. +typedef enum +{ + nativeModifierFlagShift = (1 << 17), + nativeModifierFlagControl = (1<< 18), + nativeModifierFlagOption = (1 << 19), + nativeModifierFlagCommand = (1 << 20) +} NativeModifierFlag; + +static const ModifierMapInfo modifier_map[] = { + {nativeModifierFlagShift, modifierKeyStateShift, 0x102, 0x104, {0x12}, {0x59}, 0 }, // Shift. + {nativeModifierFlagControl, modifierKeyStateControl, 0x101, 0x2100, {0x14}, {0xe0, 0x14}, 0}, // Control. + {nativeModifierFlagOption, modifierKeyStateAlt, 0x120, 0x140, {0x11}, {0xe0, 0x11}, 0}, // Alt. + {nativeModifierFlagCommand, modifierKeyStateCommand, 0x100108, 0x100110, {0xe0, 0x1f}, {0xe0, 0x27}, 1}, // Command. + {0x1<<31, 0, 0, 0, {0}, {0}, 0 }, +}; + +int get_virtual_key_index(size_t k) +{ + if (key_map[k].virtual_key[0] == 0) return 1; + + for (int i = 1; i < MAX_KEYBOARD_LAYOUTS; i++) + { + if (key_map[k].virtual_key[i] == 0xFFFF) break; + if (i == keyboardType) return i; + } + + return 0; +} + +const uint8_t * +keyboard_map_key(uint32_t native_scancode) +{ + size_t k; + int index; + + for (k = 0; key_map[k].virtual_key[0] != 0xFFFF; k++) { + index = get_virtual_key_index(k); + + if (key_map[k].virtual_key[index] == native_scancode) { + return key_map[k].set_2; + } + } + return NULL; +} + +void keyboard_handle_modifier_keys(uint mask) +{ + size_t k; + + for (k = 0; modifier_map[k].modifierMask != (1U << 31); k++) + { + int state = modifierState.keyState[modifier_map[k].checkMask]; + uint modifierMask = modifier_map[k].modifierMask; + + if ((mask & modifierMask) != 0) + { + if (modifier_map[k].simulateMenuButton && config.mousetwobutton && state == 0) + { + state = 3; + mouse_mouse_press(4); + } + else + { + if ((mask & modifier_map[k].maskLeft) == modifier_map[k].maskLeft && (state & 1) == 0) + { + state |= 1; + keyboard_key_press(modifier_map[k].set_2_left); + } + + if ((mask & modifier_map[k].maskRight) == modifier_map[k].maskRight && (state & 2) == 0) + { + state |= 2; + keyboard_key_press(modifier_map[k].set_2_right); + } + } + } + else if ((mask & modifierMask) == 0 && state != 0) + { + if (config.mousetwobutton && modifier_map[k].simulateMenuButton) + { + state = 0; + mouse_mouse_release(4); + } + else + { + if (state & 1) + { + state &= ~1; + keyboard_key_release(modifier_map[k].set_2_left); + } + if (state & 2) + { + state &= ~2; + keyboard_key_release(modifier_map[k].set_2_right); + } + } + } + + modifierState.keyState[modifier_map[k].checkMask] = state; + } +} + +void keyboard_reset_modifiers(int sendReleaseEvent) +{ + size_t k; + + for (k = 0; modifier_map[k].modifierMask != (1U << 31); k++) + { + int state = modifierState.keyState[modifier_map[k].checkMask]; + + if (sendReleaseEvent) + { + if (state & 1) + { + keyboard_key_release(modifier_map[k].set_2_left); + } + if (state & 2) + { + keyboard_key_release(modifier_map[k].set_2_right); + } + } + + modifierState.keyState[modifier_map[k].checkMask] = 0; + } +} + +void keyboard_configure_layout(const char *layoutName) +{ + if (!strcmp(layoutName, "British")) keyboardType = keyboardLayoutBritish; + else if (!strcasecmp(layoutName, "French")) keyboardType = keyboardLayoutFrench; + else keyboardType = keyboardLayoutUndefined; + + if (keyboardType == keyboardLayoutUndefined) + { + fprintf(stderr, "Unsupported keyboard layout '%s' - reverting to 'British' (0).\n", layoutName); + keyboardType = keyboardLayoutBritish; + } + else + { + fprintf(stderr, "Using keyboard layout '%s' (%d).\n", layoutName, keyboardType); + } +} + +int keyboard_check_special_keys() +{ + return (modifierState.keyState[modifierKeyStateControl] != 0 && modifierState.keyState[modifierKeyStateCommand] != 0); +} --- /dev/null 2020-05-14 17:35:51.000000000 +0100 +++ src/qt5/keyboard_macosx.h 2020-05-07 21:05:20.000000000 +0100 @@@@ -0,0 +1,40 @@@@ +/* + RPCEmu - An Acorn system emulator + + Copyright (C) 2017 Matthew Howkins + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef __KEYBOARD_MACOSX_H__ +#define __KEYBOARD_MACOSX_H__ + +#include + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +extern void keyboard_handle_modifier_keys(uint32_t mask); +extern void keyboard_reset_modifiers(int sendReleaseEvent); +extern void keyboard_configure_layout(const char *layoutName); +extern int keyboard_check_special_keys(); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif + --- original/qt5/main_window.cpp 2020-05-06 20:19:23.000000000 +0100 +++ src/qt5/main_window.cpp 2020-05-07 21:14:11.000000000 +0100 @@@@ -31,7 +31,12 @@@@ #if defined(Q_OS_WIN32) #include "Windows.h" -#endif /* Q_OS_WIN32 */ +#endif /* Q_OS_WIN32 */ + +#if defined(Q_OS_MACOS) +#include "macosx/events-macosx.h" +#include "keyboard_macosx.h" +#endif /* Q_OS_MACOS */ #include "rpcemu.h" #include "keyboard.h" @@@@ -66,6 +71,9 @@@@ void MainDisplay::mouseMoveEvent(QMouseEvent *event) { + // Ignore mouse events if the application is terminating. + if (quited) return; + if((!pconfig_copy->mousehackon && mouse_captured) || full_screen) { QPoint middle; @@@@ -90,6 +98,9 @@@@ void MainDisplay::mousePressEvent(QMouseEvent *event) { + // Ignore mouse events if the application is terminating. + if (quited) return; + // Handle turning on mouse capture in capture mouse mode if(!pconfig_copy->mousehackon) { if(!mouse_captured) { @@@@ -110,6 +121,9 @@@@ void MainDisplay::mouseReleaseEvent(QMouseEvent *event) { + // Ignore mouse events if the application is terminating. + if (quited) return; + if (event->button() & 7) { emit this->emulator.mouse_release_signal(event->button() & 7); } @@@@ -443,6 +457,11 @@@@ // Clear the list of keys considered to be held in the host held_keys.clear(); + +#if defined(Q_OS_MACOS) + emit this->emulator.modifier_keys_reset_signal(); +#endif /* Q_OS_MACOS */ + } /** @@@@ -505,53 +524,18 @@@@ } // Special case, check for Ctrl-End, our multi purpose do clever things key - if((Qt::Key_End == event->key()) && (event->modifiers() & Qt::ControlModifier)) { - if(full_screen) { - // Change Full Screen -> Windowed - - display->set_full_screen(false); - - int host_xsize, host_ysize; - display->get_host_size(host_xsize, host_ysize); - display->setFixedSize(host_xsize, host_ysize); - - menuBar()->setVisible(true); - this->showNormal(); - this->setFixedSize(this->sizeHint()); - - full_screen = false; - - // Request redraw of display - display->update(); - - // If we were in mousehack mode before entering fullscreen - // return to it now - if(reenable_mousehack) { - emit this->emulator.mouse_hack_signal(); - } - reenable_mousehack = false; - - // If we were in mouse capture mode before entering fullscreen - // and we hadn't captured the mouse, display the host cursor now - if(!config_copy.mousehackon && !mouse_captured) { - this->display->setCursor(Qt::ArrowCursor); - } - - return; - } else if(!pconfig_copy->mousehackon && mouse_captured) { - // Turn off mouse capture - mouse_captured = 0; - - // show pointer in mouse capture mode when it's not been captured - this->display->setCursor(Qt::ArrowCursor); - - return; - } + if((Qt::Key_End == event->key()) && (event->modifiers() & Qt::ControlModifier)) + { + processMagicKeys(); } // Regular case pass key press onto the emulator if (!event->isAutoRepeat()) { - native_keypress_event(event->nativeScanCode()); + #if defined(Q_OS_MACOS) + native_keypress_event(event->nativeVirtualKey(), event->nativeModifiers()); + #else + native_keypress_event(event->nativeScanCode(), event->nativeModifiers()); + #endif /* Q_OS_MACOS */ } } @@@@ -571,7 +555,11 @@@@ // Regular case pass key release onto the emulator if (!event->isAutoRepeat()) { - native_keyrelease_event(event->nativeScanCode()); +#if defined(Q_OS_MACOS) + native_keyrelease_event(event->nativeVirtualKey(), event->nativeModifiers()); +#else + native_keyrelease_event(event->nativeScanCode(), event->nativeModifiers()); +#endif /* Q_OS_MACOS */ } } @@@@ -581,8 +569,25 @@@@ * @@param scan_code Native scan code of key */ void -MainWindow::native_keypress_event(unsigned scan_code) +MainWindow::native_keypress_event(unsigned scan_code, unsigned modifiers) { +#if defined(Q_OS_MACOS) + if (!(scan_code == 0 && modifiers == 0)) + { + // Check the key isn't already marked as held down (else ignore) + // (to deal with potentially inconsistent host messages) + bool found = (std::find(held_keys.begin(), held_keys.end(), scan_code) != held_keys.end()); + + if (!found) { + // Add the key to the list of held_keys, that will be released + // when the window loses the focus + held_keys.insert(held_keys.end(), scan_code); + + emit this->emulator.key_press_signal(scan_code); + } + } +#else + // Check the key isn't already marked as held down (else ignore) // (to deal with potentially inconsistent host messages) bool found = (std::find(held_keys.begin(), held_keys.end(), scan_code) != held_keys.end()); @@@@ -592,8 +597,9 @@@@ // when the window loses the focus held_keys.insert(held_keys.end(), scan_code); - emit this->emulator.key_press_signal(scan_code); + } +#endif } /** @@@@ -602,8 +608,26 @@@@ * @@param scan_code Native scan code of key */ void -MainWindow::native_keyrelease_event(unsigned scan_code) +MainWindow::native_keyrelease_event(unsigned scan_code, unsigned modifiers) { +#if defined(Q_OS_MACOS) + + if (!(scan_code == 0 && modifiers == 0)) + { + // Check the key is marked as held down (else ignore) + // (to deal with potentially inconsistent host messages) + bool found = (std::find(held_keys.begin(), held_keys.end(), scan_code) != held_keys.end()); + + if (found) { + // Remove the key from the list of held_keys, that will be released + // when the window loses the focus + held_keys.remove(scan_code); + + emit this->emulator.key_release_signal(scan_code); + } + } + +#else // Check the key is marked as held down (else ignore) // (to deal with potentially inconsistent host messages) bool found = (std::find(held_keys.begin(), held_keys.end(), scan_code) != held_keys.end()); @@@@ -615,6 +639,7 @@@@ emit this->emulator.key_release_signal(scan_code); } +#endif } void @@@@ -1312,7 +1337,13 @@@@ if(!pconfig_copy->mousehackon) { if(mouse_captured) { + +#if defined(Q_OS_MACOS) + capture_text = " Press CTRL-COMMAND to release mouse"; +#else capture_text = " Press CTRL-END to release mouse"; +#endif + } else { capture_text = " Click to capture mouse"; } @@@@ -1415,6 +1446,52 @@@@ } } +void +MainWindow::processMagicKeys() +{ + if(full_screen) { + // Change Full Screen -> Windowed + + display->set_full_screen(false); + + int host_xsize, host_ysize; + display->get_host_size(host_xsize, host_ysize); + display->setFixedSize(host_xsize, host_ysize); + + menuBar()->setVisible(true); + this->showNormal(); + this->setFixedSize(this->sizeHint()); + + full_screen = false; + + // Request redraw of display + display->update(); + + // If we were in mousehack mode before entering fullscreen + // return to it now + if(reenable_mousehack) { + emit this->emulator.mouse_hack_signal(); + } + reenable_mousehack = false; + + // If we were in mouse capture mode before entering fullscreen + // and we hadn't captured the mouse, display the host cursor now + if(!config_copy.mousehackon && !mouse_captured) { + this->display->setCursor(Qt::ArrowCursor); + } + + return; + } else if(!pconfig_copy->mousehackon && mouse_captured) { + // Turn off mouse capture + mouse_captured = 0; + + // show pointer in mouse capture mode when it's not been captured + this->display->setCursor(Qt::ArrowCursor); + + return; + } +} + #if defined(Q_OS_WIN32) /** * windows pre event handler used by us to modify some default behaviour @@@@ -1473,3 +1550,46 @@@@ return false; } #endif // Q_OS_WIN32 + +#if defined(Q_OS_MACOS) +/** + * On OS X, handle additional events for modifier keys. The normal key press/release + * events do not differentiate between left and right. + * + * @@param eventType unused + * @@param message window event NSEvent data + * @@param result unused + * @@return bool of whether we've handled the event (true) or OS X/QT should deal with it (false) + */ +bool +MainWindow::nativeEvent(const QByteArray &eventType, void *message, long *result) +{ + Q_UNUSED(eventType); + Q_UNUSED(result); + + NativeEvent *event = handle_native_event(message); + if (!event->processed) + { + free(event); + return false; + } + + if (event->eventType == nativeEventTypeModifiersChanged) + { + // Modifier key state has changed. + emit this->emulator.modifier_keys_changed_signal(event->modifierMask); + + if (keyboard_check_special_keys()) + { + // Magic key combination to release mouse capture. + processMagicKeys(); + } + + free(event); + } + + return true; +} + +#endif /* Q_OS_MACOS */ + --- original/qt5/main_window.h 2020-05-06 20:19:23.000000000 +0100 +++ src/qt5/main_window.h 2020-05-07 21:14:11.000000000 +0100 @@@@ -109,7 +109,7 @@@@ void closeEvent(QCloseEvent *event) Q_DECL_OVERRIDE; void keyPressEvent(QKeyEvent *event) Q_DECL_OVERRIDE; void keyReleaseEvent(QKeyEvent *event) Q_DECL_OVERRIDE; -#if defined(Q_OS_WIN32) +#if defined(Q_OS_WIN32) || defined(Q_OS_MACOS) bool nativeEvent(const QByteArray &eventType, void *message, long *result) Q_DECL_OVERRIDE; #endif /* Q_OS_WIN32 */ @@@@ -165,9 +165,11 @@@@ void cdrom_menu_selection_update(const QAction *cdrom_action); - void native_keypress_event(unsigned scan_code); - void native_keyrelease_event(unsigned scan_code); + void native_keypress_event(unsigned scan_code, unsigned modifiers); + void native_keyrelease_event(unsigned scan_code, unsigned modifiers); void release_held_keys(); + + void processMagicKeys(); bool full_screen; bool reenable_mousehack; ///< Did we disable mousehack entering fullscreen and have to reenable it on leaving fullscreen? --- /dev/null 2020-05-14 17:35:51.000000000 +0100 +++ src/macosx/network-macosx.c 2020-05-07 21:05:20.000000000 +0100 @@@@ -0,0 +1,97 @@@@ +/* + RPCEmu - An Acorn system emulator + + Copyright (C) 2005-2010 Sarah Walker + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* RPCemu networking */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "rpcemu.h" +#include "mem.h" +#include "podules.h" +#include "network.h" + +int +network_plt_init(void) +{ + // Do nothing on a Mac, as TUN/TAP is not supported. + return 0; +} + +/** + * Shutdown any running network components. + * + * Called on program shutdown and program reset after + * configuration has changed. + */ +void +network_plt_reset(void) +{ + // Do nothing on a Mac, as TUN/TAP is not supported. +} + +uint32_t +network_plt_rx(uint32_t errbuf, uint32_t mbuf, uint32_t rxhdr, uint32_t *dataavail) +{ + NOT_USED(errbuf); + NOT_USED(mbuf); + NOT_USED(rxhdr); + NOT_USED(dataavail); + + // Do nothing on a Mac, as TUN/TAP is not supported. + return 0; +} + +uint32_t +network_plt_tx(uint32_t errbuf, uint32_t mbufs, uint32_t dest, uint32_t src, uint32_t frametype) +{ + NOT_USED(errbuf); + NOT_USED(mbufs); + NOT_USED(dest); + NOT_USED(src); + NOT_USED(frametype); + + // Do nothing on a Mac, as TUN/TAP is not supported. + return 0; +} + +void +network_plt_setirqstatus(uint32_t address) +{ + NOT_USED(address); + + // Do nothing on a Mac, as TUN/TAP is not supported. +} --- original/network.c 2020-05-06 20:19:23.000000000 +0100 +++ src/network.c 2020-05-07 21:14:11.000000000 +0100 @@@@ -80,8 +80,13 @@@@ filebase = chunkbase + (8 * 2) + 4; // required size for two entries poduleromsize = filebase + ((sizeof(description) + 3) & ~3u); // Word align description string - // Add on size for driver module if it can be opened successfully - f = fopen("netroms/EtherRPCEm,ffa", "rb"); + char filename[512]; + snprintf(filename,sizeof(filename), "%snetroms/EtherRPCEm,ffa", rpcemu_get_datadir()); + + rpclog("network_rom_init: Attempting to load Ethernet ROM from '%s'\n", filename); + + // Add on size for driver module if it can be opened successfully + f = fopen(filename, "rb"); if (f != NULL) { long len; @@@@ -124,6 +129,8 @@@@ if (len == module_file_size) { // Load was OK len = (len + 3) & ~3u; makechunk(0x81, filebase, (uint32_t) len); // 0x81 = RISC OS, ROM + + rpclog("network_rom_init: Successfuly loaded 'EtherRPCEm,ffa' into podulerom\n"); } } } --- /dev/null 2020-05-14 17:35:51.000000000 +0100 +++ src/macosx/preferences-macosx.h 2020-05-07 21:05:20.000000000 +0100 @@@@ -0,0 +1,38 @@@@ +///* +// RPCEmu - An Acorn system emulator +// +// Copyright (C) 2017 Matthew Howkins +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +// */ + +#ifndef __PREFERENCES_MACOSX_H__ +#define __PREFERENCES_MACOSX_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +extern void init_preferences(void); +extern void preferences_set_data_directory(const char *path); +extern const char *preferences_get_data_directory(); + +extern bool promptForDataDirectory; + +#ifdef __cplusplus +} +#endif + +#endif // __PREFERENCES_MACOSX_H__ --- /dev/null 2020-05-14 17:35:51.000000000 +0100 +++ src/macosx/preferences-macosx.m 2020-05-07 21:05:20.000000000 +0100 @@@@ -0,0 +1,86 @@@@ +/* + RPCEmu - An Acorn system emulator + + Copyright (C) 2017 Matthew Howkins + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#define UNUSED(x) (void)(x) + +#include +#include +#include + +#include +#include +#include + +#include "rpcemu.h" + +bool promptForDataDirectory; +NSString* const KeyDataDirectory = @@"DataDirectory"; + +void init_preferences(void) +{ + NSMutableDictionary *defaultValues = [NSMutableDictionary dictionary]; + [defaultValues setObject: @@"" forKey:KeyDataDirectory]; + + [[NSUserDefaults standardUserDefaults] registerDefaults: defaultValues]; + + NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; + + // Check to see if there is a proper path for the data directory. + // If not, prompt for one. + NSString *dataDirectory = [defaults stringForKey: KeyDataDirectory]; + if (dataDirectory == nil || [dataDirectory length] == 0) + { + promptForDataDirectory = true; + } + else + { + const char *str = [dataDirectory UTF8String]; + + // Check the folder exists. + DIR *ptr = opendir(str); + if (ptr) + { + closedir(ptr); + rpcemu_set_datadir(str); + + promptForDataDirectory = false; + } + else + { + promptForDataDirectory = true; + } + } +} + +void preferences_set_data_directory(const char *path) +{ + NSString *dataDirectory = [NSString stringWithUTF8String: path]; + + NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; + [defaults setObject:dataDirectory forKey:KeyDataDirectory]; +} + +const char* preferences_get_data_directory() +{ + NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; + NSString *path = [defaults stringForKey: KeyDataDirectory]; + + return [path UTF8String]; +} --- original/rpc-machdep.c 2020-05-06 20:19:23.000000000 +0100 +++ src/rpc-machdep.c 2020-05-07 21:05:20.000000000 +0100 @@@@ -26,7 +26,39 @@@@ be, but currently this version is used by Linux, all the other autoconf based builds and Windows. Only Mac OS X GUI version needs to override */ +#ifdef __APPLE__ +#include + +static char datadir[512] = ""; + +int rpcemu_set_datadir(const char *path) +{ + int len = strlen(path); + if (len == 0) return 0; + + if (path[len - 1] != '/') + { + snprintf(datadir, 512, "%s/", path); + } + else + { + strncpy(datadir, path, 512); + } + + DIR *ptr = opendir(datadir); + if (ptr) + { + closedir(ptr); + return 1; + } + + return 0; +} + +#else static char datadir[512] = "./"; +#endif + static char logpath[1024] = ""; /** --- /dev/null 2020-05-14 17:35:51.000000000 +0100 +++ src/rpc-macosx.c 2020-05-07 21:05:20.000000000 +0100 @@@@ -0,0 +1,87 @@@@ +/* + RPCEmu - An Acorn system emulator + + Copyright (C) 2005-2010 Sarah Walker + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "rpcemu.h" +#include "mem.h" +#include "sound.h" +#include "vidc20.h" + + + + +/** + * Return disk space information about a file system. + * + * @@param path Pathname of object within file system + * @@param d Pointer to disk_info structure that will be filled in + * @@return On success 1 is returned, on error 0 is returned + */ +int +path_disk_info(const char *path, disk_info *d) +{ + struct statvfs s; + int ret; + + assert(path != NULL); + assert(d != NULL); + + if ((ret = statvfs(path, &s)) != 0) { + return 0; + } + + d->size = (uint64_t) s.f_blocks * (uint64_t) s.f_frsize; + d->free = (uint64_t) s.f_bavail * (uint64_t) s.f_frsize; + + return 1; +} + +/** + * Log details about the current Operating System version. + * + * This function should work on all Unix and Unix-like systems. + * + * Called during program start-up. + */ +void +rpcemu_log_os(void) +{ + struct utsname u; + + if (uname(&u) == -1) { + rpclog("OS: Could not determine: %s\n", strerror(errno)); + return; + } + + rpclog("OS: SysName = %s\n", u.sysname); + rpclog("OS: Release = %s\n", u.release); + rpclog("OS: Version = %s\n", u.version); + rpclog("OS: Machine = %s\n", u.machine); +} --- original/qt5/rpc-qt5.cpp 2020-05-06 20:19:23.000000000 +0100 +++ src/qt5/rpc-qt5.cpp 2020-05-08 14:36:13.000000000 +0100 @@@@ -47,6 +47,15 @@@@ #include "network.h" #include "network-nat.h" +#if defined(Q_OS_MACOS) +#include "choose_dialog.h" + +#include "macosx/preferences-macosx.h" +#include "macosx/hid-macosx.h" + +#include "keyboard_macosx.h" +#endif /* Q_OS_MACOS */ + #if defined(Q_OS_WIN32) #include "cdrom-ioctl.h" @@@@ -398,6 +407,23 @@@@ } // extern "C" +#if defined(Q_OS_MACOS) + +int rpcemu_choose_datadirectory() +{ + ChooseDialog dialog; + if (dialog.exec() == QDialog::Accepted) + { + const char *path = preferences_get_data_directory(); + + return rpcemu_set_datadir(path); + } + + return 0; +} + +#endif + /** * Program entry point * @@@@ -416,6 +442,20 @@@@ // Add a program icon QApplication::setWindowIcon(QIcon(":/rpcemu_icon.png")); + +#if defined(Q_OS_MACOS) + init_preferences(); + + // If there is not a data directory in the application preferences, prompt for one. + // This will also prompt if the "Command" key is held down while the application loads. + if (promptForDataDirectory || (QApplication::queryKeyboardModifiers() & Qt::ShiftModifier) != 0) + { + if (!rpcemu_choose_datadirectory()) + { + return 0; + } + } +#endif // start enough of the emulator system to allow // the GUI to initialise (e.g. load the config to init @@@@ -438,6 +478,11 @@@@ QThread::connect(emulator, &Emulator::finished, emu_thread, &QThread::quit); QThread::connect(emulator, &Emulator::finished, emulator, &Emulator::deleteLater); QThread::connect(emu_thread, &QThread::finished, emu_thread, &QThread::deleteLater); + +#if defined(Q_OS_MACOS) + // Initialise the HID manager for CAPS LOCK key events. + init_hid_manager(); +#endif // Create Main Window MainWindow main_window(*emulator); @@@@ -473,6 +518,14 @@@@ connect(this, &Emulator::key_release_signal, this, &Emulator::key_release); + +#if defined(Q_OS_MACOS) + // Modifier keys on a Mac must be handled separately, as there is no way of telling + // left or right from the key press and key release events due to a lack of scan codes. + + connect(this, &Emulator::modifier_keys_changed_signal, this, &Emulator::modifier_keys_changed); + connect(this, &Emulator::modifier_keys_reset_signal, this, &Emulator::modifier_keys_reset); +#endif /* Q_OS_MACOS */ connect(this, &Emulator::mouse_move_signal, this, &Emulator::mouse_move); connect(this, &Emulator::mouse_move_relative_signal, this, &Emulator::mouse_move_relative); @@@@ -630,6 +683,27 @@@@ keyboard_key_release(scan_codes); } +#if defined(Q_OS_MACOS) + +/** + * Modifier keys changed + * @@param mask The modifier key mask from the original NSEvent + */ +void Emulator::modifier_keys_changed(unsigned mask) +{ + keyboard_handle_modifier_keys(mask); +} + +/** + * Modifier keys reset + */ +void Emulator::modifier_keys_reset() +{ + keyboard_reset_modifiers(true); +} + +#endif /* Q_OS_MACOS */ + /** * Mouse has moved in absolute position (mousehack mode) * --- original/qt5/rpc-qt5.h 2020-05-06 20:19:23.000000000 +0100 +++ src/qt5/rpc-qt5.h 2020-05-07 21:14:11.000000000 +0100 @@@@ -49,6 +49,11 @@@@ void key_press_signal(unsigned scan_code); void key_release_signal(unsigned scan_code); + +#if defined(Q_OS_MACOS) + void modifier_keys_changed_signal(unsigned mask); + void modifier_keys_reset_signal(); +#endif /* Q_OS_MACOS */ void mouse_move_signal(int x, int y); void mouse_move_relative_signal(int dx, int dy); @@@@ -78,6 +83,11 @@@@ void key_press(unsigned scan_code); void key_release(unsigned scan_code); + +#if defined(Q_OS_MACOS) + void modifier_keys_changed(unsigned mask); + void modifier_keys_reset(); +#endif /* Q_OS_MACOS */ void mouse_move(int x, int y); void mouse_move_relative(int dx, int dy); --- original/rpcemu.h 2020-05-06 20:19:23.000000000 +0100 +++ src/rpcemu.h 2020-05-07 21:14:11.000000000 +0100 @@@@ -72,7 +72,7 @@@@ /* Note that networking is currently supported on Mac OS X with the Cocoa GUI version but not with the Allegro GUI. */ #if defined __linux || defined __linux__ || defined WIN32 || defined _WIN32 || \ - defined RPCEMU_COCOA_GUI + defined RPCEMU_COCOA_GUI || __APPLE__ #define RPCEMU_NETWORKING #endif @@@@ -169,6 +169,10 @@@@ extern uint32_t inscount; extern int cyccount; +#ifdef __APPLE__ +extern int rpcemu_set_datadir(const char *path); +#endif + /* These functions can optionally be overridden by a platform. If not needed to be overridden, there is a generic version in rpc-machdep.c */ extern const char *rpcemu_get_datadir(void); --- original/qt5/rpcemu.pro 2020-05-06 20:19:23.000000000 +0100 +++ src/qt5/rpcemu.pro 2020-05-14 17:33:58.000000000 +0100 @@@@ -6,6 +6,10 @@@@ QT += core widgets gui multimedia INCLUDEPATH += ../ +macx { + INCLUDEPATH += ../macosx +} + # This ensures that using switch with enum requires every value to be handled QMAKE_CFLAGS += -Werror=switch QMAKE_CXXFLAGS += -Werror=switch @@@@ -61,7 +65,7 @@@@ plt_sound.cpp # NAT Networking -linux | win32 { +linux | win32 | macx { HEADERS += ../network-nat.h SOURCES += ../network-nat.c @@@@ -141,10 +145,38 @@@@ network_dialog.h } -unix { - SOURCES += keyboard_x.c \ - ../hostfs-unix.c \ - ../rpc-linux.c +!macx { + unix { + SOURCES += keyboard_x.c \ + ../hostfs-unix.c \ + ../rpc-linux.c + } +} + +macx { + SOURCES += ../network.c \ + network_dialog.cpp \ + keyboard_macosx.c \ + ../hostfs-macosx.c \ + ../rpc-macosx.c \ + ../macosx/hid-macosx.m \ + ../macosx/events-macosx.m \ + ../macosx/preferences-macosx.m \ + ../macosx/network-macosx.c \ + ../macosx/system-macosx.m \ + choose_dialog.cpp + + HEADERS += ../network.h \ + network_dialog.h \ + keyboard_macosx.h \ + ../macosx/hid-macosx.h \ + ../macosx/events-macosx.h \ + ../macosx/preferences-macosx.h \ + ../macosx/system-macosx.h \ + choose_dialog.h + + ICON = ../macosx/rpcemu.icns + } # Place exes in top level directory @@@@ -189,4 +221,13 @@@@ TARGET = $$join(TARGET, , , -debug) } -LIBS += +!macx { + LIBS += +} + +macx { + LIBS += -framework coreFoundation -framework IOKit -framework Foundation -framework Carbon + + QMAKE_INFO_PLIST = ../macosx/Info.plist +} + --- original/qt5/settings.cpp 2020-05-06 20:19:23.000000000 +0100 +++ src/qt5/settings.cpp 2020-05-07 21:14:11.000000000 +0100 @@@@ -42,7 +42,7 @@@@ snprintf(filename, sizeof(filename), "%srpc.cfg", rpcemu_get_datadir()); - QSettings settings("rpc.cfg", QSettings::IniFormat); + QSettings settings(filename, QSettings::IniFormat); /* Copy the contents of the configfile to the log */ QStringList keys = settings.childKeys(); @@@@ -199,7 +199,7 @@@@ snprintf(filename, sizeof(filename), "%srpc.cfg", rpcemu_get_datadir()); - QSettings settings("rpc.cfg", QSettings::IniFormat); + QSettings settings(filename, QSettings::IniFormat); char s[256]; --- /dev/null 2020-05-14 17:35:51.000000000 +0100 +++ src/macosx/system-macosx.h 2020-05-07 21:05:20.000000000 +0100 @@@@ -0,0 +1,34 @@@@ +/* +RPCEmu - An Acorn system emulator + +Copyright (C) 2017 Peter Howkins + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#ifndef __SYSTEM_MACOSX_H__ +#define __SYSTEM_MACOSX_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +extern unsigned int get_macosx_version(void); + +#ifdef __cplusplus +} +#endif + +#endif --- /dev/null 2020-05-14 17:35:51.000000000 +0100 +++ src/macosx/system-macosx.m 2020-05-07 21:05:20.000000000 +0100 @@@@ -0,0 +1,35 @@@@ +/* +RPCEmu - An Acorn system emulator + +Copyright (C) 2017 Matthew Howkins + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include +#include + +#include +#include + +unsigned int get_macosx_version(void) +{ + NSOperatingSystemVersion version; + + version = [[NSProcessInfo processInfo] operatingSystemVersion]; + + return (version.majorVersion << 16) | (version.minorVersion << 8) | (version.patchVersion); +} + @