Received: from mail.netbsd.org (mail.netbsd.org [199.233.217.200]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (Client CN "mail.NetBSD.org", Issuer "mail.NetBSD.org CA" (not verified)) by mollari.NetBSD.org (Postfix) with ESMTPS id 0EB8C1A9239 for ; Sun, 1 Nov 2020 20:18:35 +0000 (UTC) Received: by mail.netbsd.org (Postfix, from userid 605) id 827B184D37; Sun, 1 Nov 2020 20:18:34 +0000 (UTC) Received: from localhost (localhost [127.0.0.1]) by mail.netbsd.org (Postfix) with ESMTP id 07B5984CEF for ; Sun, 1 Nov 2020 20:18:34 +0000 (UTC) X-Virus-Scanned: amavisd-new at netbsd.org Received: from mail.netbsd.org ([127.0.0.1]) by localhost (mail.netbsd.org [127.0.0.1]) (amavisd-new, port 10025) with ESMTP id 8O3vNlnQPo1E for ; Sun, 1 Nov 2020 20:18:30 +0000 (UTC) Received: from cvs.NetBSD.org (ivanova.NetBSD.org [IPv6:2001:470:a085:999:28c:faff:fe03:5984]) by mail.netbsd.org (Postfix) with ESMTP id 6AAED84CED for ; Sun, 1 Nov 2020 20:18:30 +0000 (UTC) Received: by cvs.NetBSD.org (Postfix, from userid 500) id 5C8C6FB28; Sun, 1 Nov 2020 20:18:30 +0000 (UTC) Content-Transfer-Encoding: 7bit Content-Type: multipart/mixed; boundary="_----------=_160426191070360" MIME-Version: 1.0 Date: Sun, 1 Nov 2020 20:18:30 +0000 From: "Jonathan Schleifer" Subject: CVS commit: pkgsrc/emulators/rpcemu To: pkgsrc-changes@NetBSD.org Reply-To: js@netbsd.org X-Mailer: log_accum Message-Id: <20201101201830.5C8C6FB28@cvs.NetBSD.org> Sender: pkgsrc-changes-owner@NetBSD.org List-Id: pkgsrc-changes.NetBSD.org Precedence: bulk List-Unsubscribe: This is a multi-part message in MIME format. --_----------=_160426191070360 Content-Disposition: inline Content-Transfer-Encoding: 8bit Content-Type: text/plain; charset="US-ASCII" Module Name: pkgsrc Committed By: js Date: Sun Nov 1 20:18:30 UTC 2020 Modified Files: pkgsrc/emulators/rpcemu: Makefile Added Files: pkgsrc/emulators/rpcemu/files: Info.plist rpcemu-0.9.3-mac-patch-v1.patch Removed Files: pkgsrc/emulators/rpcemu/files: rpcemu-0.9.1-mac-v1.patch Log Message: emulators/rpcemu: Update to a newer patchset for macOS support To generate a diff of this commit: cvs rdiff -u -r1.2 -r1.3 pkgsrc/emulators/rpcemu/Makefile cvs rdiff -u -r0 -r1.1 pkgsrc/emulators/rpcemu/files/Info.plist \ pkgsrc/emulators/rpcemu/files/rpcemu-0.9.3-mac-patch-v1.patch cvs rdiff -u -r1.1 -r0 \ pkgsrc/emulators/rpcemu/files/rpcemu-0.9.1-mac-v1.patch Please note that diffs are not public domain; they are subject to the copyright notices on the relevant files. --_----------=_160426191070360 Content-Disposition: inline Content-Length: 74514 Content-Transfer-Encoding: binary Content-Type: text/x-diff; charset=us-ascii Modified files: Index: pkgsrc/emulators/rpcemu/Makefile diff -u pkgsrc/emulators/rpcemu/Makefile:1.2 pkgsrc/emulators/rpcemu/Makefile:1.3 --- pkgsrc/emulators/rpcemu/Makefile:1.2 Sat Oct 24 16:53:41 2020 +++ pkgsrc/emulators/rpcemu/Makefile Sun Nov 1 20:18:30 2020 @@ -1,7 +1,7 @@ -# $NetBSD: Makefile,v 1.2 2020/10/24 16:53:41 js Exp $ +# $NetBSD: Makefile,v 1.3 2020/11/01 20:18:30 js Exp $ DISTNAME= rpcemu-0.9.3 -PKGREVISION= 1 +PKGREVISION= 2 CATEGORIES= emulators MASTER_SITES= http://www.marutan.net/rpcemu/cgi/download.php?sFName=${PKGVERSION_NOREV}/ @@ -18,7 +18,9 @@ INSTALLATION_DIRS+= bin post-patch: ${RUN} cd ${WRKSRC}/src && \ - ${PATCH} -p0 <${FILESDIR}/rpcemu-0.9.1-mac-v1.patch + ${PATCH} -p1 <${FILESDIR}/rpcemu-0.9.3-mac-patch-v1.patch + ${RUN} ${MKDIR} ${WRKSRC}/src/macosx + ${RUN} cp ${FILESDIR}/Info.plist ${WRKSRC}/src/macosx/Info.plist do-configure: cd ${WRKSRC} && ${QTDIR}/bin/qmake src/qt5/rpcemu.pro Added files: Index: pkgsrc/emulators/rpcemu/files/Info.plist diff -u /dev/null pkgsrc/emulators/rpcemu/files/Info.plist:1.1 --- /dev/null Sun Nov 1 20:18:30 2020 +++ pkgsrc/emulators/rpcemu/files/Info.plist Sun Nov 1 20:18:30 2020 @@ -0,0 +1,26 @@ + + + + + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleGetInfoString + Created by Qt/QMake + CFBundleIconFile + ${ASSETCATALOG_COMPILER_APPICON_NAME} + CFBundleIdentifier + org.marutan.rpcemu + CFBundlePackageType + APPL + CFBundleSignature + ${QMAKE_PKGINFO_TYPEINFO} + LSMinimumSystemVersion + ${MACOSX_DEPLOYMENT_TARGET} + NOTE + This file was generated by Qt/QMake. + NSPrincipalClass + NSApplication + NSSupportsAutomaticGraphicsSwitching + + + Index: pkgsrc/emulators/rpcemu/files/rpcemu-0.9.3-mac-patch-v1.patch diff -u /dev/null pkgsrc/emulators/rpcemu/files/rpcemu-0.9.3-mac-patch-v1.patch:1.1 --- /dev/null Sun Nov 1 20:18:30 2020 +++ pkgsrc/emulators/rpcemu/files/rpcemu-0.9.3-mac-patch-v1.patch Sun Nov 1 20:18:30 2020 @@ -0,0 +1,2264 @@ +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); ++} ++ --_----------=_160426191070360--