pkgsrc-Changes archive

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]

CVS commit: pkgsrc/emulators/rpcemu



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.

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 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd";>
+<plist version="1.0">
+<dict>
+       <key>CFBundleExecutable</key>
+       <string>${EXECUTABLE_NAME}</string>
+       <key>CFBundleGetInfoString</key>
+       <string>Created by Qt/QMake</string>
+       <key>CFBundleIconFile</key>
+       <string>${ASSETCATALOG_COMPILER_APPICON_NAME}</string>
+       <key>CFBundleIdentifier</key>
+       <string>org.marutan.rpcemu</string>
+       <key>CFBundlePackageType</key>
+       <string>APPL</string>
+       <key>CFBundleSignature</key>
+       <string>${QMAKE_PKGINFO_TYPEINFO}</string>
+       <key>LSMinimumSystemVersion</key>
+       <string>${MACOSX_DEPLOYMENT_TARGET}</string>
+       <key>NOTE</key>
+       <string>This file was generated by Qt/QMake.</string>
+       <key>NSPrincipalClass</key>
+       <string>NSApplication</string>
+       <key>NSSupportsAutomaticGraphicsSwitching</key>
+       <true/>
+</dict>
+</plist>
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 <QFileDialog>
++#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("<p>Before using RPCEmu for the first time, you must select the directory <br/>"
++                        "that contains the folders and files required by the emulator, such as <br>"
++                        "ROMs, hard drive images and the HostFS share.</p>"
++                        "<p>You can show this dialogue again by holding down the Command key <br/>"
++                        "whilst the application is loading.</p>");
++
++  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 <QDialog>
++#include <QDialogButtonBox>
++#include <QGridLayout>
++#include <QLabel>
++#include <QLineEdit>
++#include <QPushButton>
++
++#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 <stddef.h>
++#include <stdint.h>
++
++#include <Cocoa/Cocoa.h>
++#include <Carbon/Carbon.h>
++#include <IOKit/hid/IOHIDLib.h>
++
++#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 <stddef.h>
++#include <stdint.h>
++
++#include <Cocoa/Cocoa.h>
++#include <Carbon/Carbon.h>
++#include <IOKit/hid/IOHIDLib.h>
++
++#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 <assert.h>
++#include <errno.h>
++#include <stdio.h>
++#include <string.h>
++
++#include <utime.h>
++#include <sys/stat.h>
++
++#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 <Carbon/Carbon.h>
++
++#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 <stdint.h>
++
++#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 <assert.h>
++#include <ctype.h>
++#include <stdio.h>
++#include <stdlib.h>
++#include <fcntl.h>
++#include <unistd.h>
++#include <sys/time.h>
++#include <sys/types.h>
++#include <sys/ioctl.h>
++#include <errno.h>
++#include <string.h>
++#include <sys/socket.h>
++#include <net/if.h>
++#include <netinet/in.h>
++#include <arpa/inet.h>
++#include <pwd.h>
++#include <grp.h>
++#include <signal.h>
++
++#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 <stddef.h>
++#include <stdint.h>
++#include <dirent.h>
++
++#include <Cocoa/Cocoa.h>
++#include <Carbon/Carbon.h>
++#include <IOKit/hid/IOHIDLib.h>
++
++#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 <dirent.h>
++
++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 <assert.h>
++#include <errno.h>
++#include <signal.h>
++#include <string.h>
++
++#include <pthread.h>
++#include <sys/statvfs.h>
++#include <sys/types.h>
++#include <sys/utsname.h>
++#include <sys/wait.h>
++
++#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 <stddef.h>
++#include <stdint.h>
++
++#include <Cocoa/Cocoa.h>
++#include <Carbon/Carbon.h>
++
++unsigned int get_macosx_version(void)
++{
++    NSOperatingSystemVersion version;
++    
++    version = [[NSProcessInfo processInfo] operatingSystemVersion];
++    
++    return (version.majorVersion << 16) | (version.minorVersion << 8) | (version.patchVersion);
++}
++



Home | Main Index | Thread Index | Old Index