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:           Sat Oct 24 16:53:41 UTC 2020

Modified Files:
        pkgsrc/emulators/rpcemu: Makefile
Added Files:
        pkgsrc/emulators/rpcemu/files: rpcemu-0.9.1-mac-v1.patch

Log Message:
emulators/rpcemu: Add a patchset that adds macOS support

This makes the keyboard work, among others.


To generate a diff of this commit:
cvs rdiff -u -r1.1 -r1.2 pkgsrc/emulators/rpcemu/Makefile
cvs rdiff -u -r0 -r1.1 \
    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.1 pkgsrc/emulators/rpcemu/Makefile:1.2
--- pkgsrc/emulators/rpcemu/Makefile:1.1        Sat Oct 24 16:33:34 2020
+++ pkgsrc/emulators/rpcemu/Makefile    Sat Oct 24 16:53:41 2020
@@ -1,6 +1,7 @@
-# $NetBSD: Makefile,v 1.1 2020/10/24 16:33:34 js Exp $
+# $NetBSD: Makefile,v 1.2 2020/10/24 16:53:41 js Exp $
 
 DISTNAME=      rpcemu-0.9.3
+PKGREVISION=   1
 CATEGORIES=    emulators
 MASTER_SITES=  http://www.marutan.net/rpcemu/cgi/download.php?sFName=${PKGVERSION_NOREV}/
 
@@ -10,10 +11,15 @@ COMMENT=    Emulator of classic Acorn compu
 LICENSE=       gnu-gpl-v2
 
 USE_LANGUAGES= c c++
+USE_TOOLS+=    patch
 TOOL_DEPENDS+= qt5-qttools-[0-9]*:../../x11/qt5-qttools
 
 INSTALLATION_DIRS+=    bin
 
+post-patch:
+       ${RUN} cd ${WRKSRC}/src && \
+               ${PATCH} -p0 <${FILESDIR}/rpcemu-0.9.1-mac-v1.patch
+
 do-configure:
        cd ${WRKSRC} && ${QTDIR}/bin/qmake src/qt5/rpcemu.pro
 

Added files:

Index: pkgsrc/emulators/rpcemu/files/rpcemu-0.9.1-mac-v1.patch
diff -u /dev/null pkgsrc/emulators/rpcemu/files/rpcemu-0.9.1-mac-v1.patch:1.1
--- /dev/null   Sat Oct 24 16:53:41 2020
+++ pkgsrc/emulators/rpcemu/files/rpcemu-0.9.1-mac-v1.patch     Sat Oct 24 16:53:41 2020
@@ -0,0 +1,1800 @@
+Patchset from http://www.riscos.info/pipermail/rpcemu/2018-November/002703.html
+to add macOS support.
+
+--- /dev/null  2018-11-18 20:18:48.000000000 +0000
++++ macosx/Info.plist  2018-11-18 20:18:14.000000000 +0000
+@@ -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>
+--- /dev/null  2018-11-18 19:57:31.000000000 +0000
++++ qt5/choose_dialog.cpp      2018-11-04 16:40:30.000000000 +0000
+@@ -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  2018-11-18 19:57:31.000000000 +0000
++++ qt5/choose_dialog.h        2018-11-04 16:34:31.000000000 +0000
+@@ -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  2018-11-18 19:56:31.000000000 +0000
++++ macosx/events-macosx.h     2018-11-02 19:57:29.000000000 +0000
+@@ -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  2018-11-18 19:56:31.000000000 +0000
++++ macosx/events-macosx.m     2018-11-02 19:57:31.000000000 +0000
+@@ -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  2018-11-18 19:56:31.000000000 +0000
++++ macosx/hid-macosx.h        2018-11-02 19:57:37.000000000 +0000
+@@ -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  2018-11-18 19:56:31.000000000 +0000
++++ macosx/hid-macosx.m        2018-11-02 19:57:40.000000000 +0000
+@@ -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  2018-11-18 19:49:33.000000000 +0000
++++ hostfs-macosx.c    2018-11-04 16:50:44.000000000 +0000
+@@ -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() */
++}
+--- hostfs.c.orig      2018-11-02 19:15:19.000000000 +0000
++++ hostfs.c   2018-11-18 19:52:41.000000000 +0000
+@@ -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;
+     }
+--- keyboard.c.orig    2018-11-02 19:15:19.000000000 +0000
++++ keyboard.c 2018-11-02 20:02:26.000000000 +0000
+@@ -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,11 @@
+       /* Mousehack reset */
+       mouse_hack.pointer = 0;
+       mouse_hack.cursor_linked = 1;
++    
++#ifdef __APPLE__
++    keyboard_reset_modifiers(0);
++#endif
++    
+ }
+ 
+ static uint8_t
+--- /dev/null  2018-11-18 19:58:10.000000000 +0000
++++ qt5/keyboard_macosx.c      2018-11-02 20:44:35.000000000 +0000
+@@ -0,0 +1,326 @@
++/*
++ 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>
++
++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];
++} 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} },                       // Shift.
++    {nativeModifierFlagControl, modifierKeyStateControl, 0x101, 0x2100, {0x14}, {0xe0, 0x14} },            // Control.
++    {nativeModifierFlagOption, modifierKeyStateAlt, 0x120, 0x140, {0x11}, {0xe0, 0x11}},                   // Alt.
++    {nativeModifierFlagCommand, modifierKeyStateCommand, 0x100108, 0x100110, {0xe0, 0x1f}, {0xe0, 0x27}},  // Command.
++    {0x1<<31, 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].maskLeft != 0xFFFFFFFF && (mask & modifier_map[k].maskLeft) == modifier_map[k].maskLeft && (state & 1) == 0)
++            {
++                state |= 1;
++                keyboard_key_press(modifier_map[k].set_2_left);
++            }
++            if (modifier_map[k].maskRight != 0xFFFFFFFF && (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 (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);
++    }
++}
+--- /dev/null  2018-11-18 19:58:10.000000000 +0000
++++ qt5/keyboard_macosx.h      2018-11-02 20:44:36.000000000 +0000
+@@ -0,0 +1,39 @@
++/*
++ 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);
++    
++#ifdef __cplusplus
++}
++#endif /* __cplusplus */
++        
++#endif
++
+--- qt5/main_window.cpp.orig   2018-11-02 19:15:19.000000000 +0000
++++ qt5/main_window.cpp        2018-11-02 21:25:14.000000000 +0000
+@@ -31,7 +31,11 @@
+ 
+ #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"
++#endif /* Q_OS_MACOS */
+ 
+ #include "rpcemu.h"
+ #include "keyboard.h"
+@@ -423,6 +427,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 */
++      
+ }
+ 
+ /**
+@@ -531,7 +540,13 @@
+ 
+       // 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 */
++              
+       }
+ }
+ 
+@@ -551,7 +566,13 @@
+ 
+       // 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_release_event(event->nativeScanCode(), event->nativeModifiers());
++#endif        /* Q_OS_MACOS */
++
+       }
+ }
+ 
+@@ -559,10 +580,28 @@
+  * Called by us with native scan-code to forward key-press to the emulator
+  *
+  * @param scan_code Native scan code of key
++ * @param modifiers Native modifiers
+  */
+ 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());
+@@ -574,16 +613,39 @@
+ 
+               emit this->emulator.key_press_signal(scan_code);
+       }
++
++#endif /* Q_OS_MACOS */
+ }
+ 
+ /**
+  * Called by us with native scan-code to forward key-release to the emulator
+  *
+  * @param scan_code Native scan code of key
++ * @param modifiers Native modifiers
+  */
+ 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());
+@@ -595,6 +657,9 @@
+ 
+               emit this->emulator.key_release_signal(scan_code);
+       }
++      
++#endif /* Q_OS_MACOS */
++      
+ }
+ 
+ void
+@@ -1480,3 +1545,38 @@
+       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);
++              free(event);
++      }
++      
++      return true;
++}
++
++#endif /* Q_OS_MACOS */
+--- qt5/main_window.h.orig     2018-11-02 19:15:19.000000000 +0000
++++ qt5/main_window.h  2018-11-02 20:59:06.000000000 +0000
+@@ -107,9 +107,9 @@
+       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 */
++#endif /* Q_OS_WIN32 or Q_OS_MACOS */
+       
+ private slots:
+       void menu_screenshot();
+@@ -163,8 +163,8 @@
+ 
+       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();
+ 
+       bool full_screen;
+--- /dev/null  2018-11-18 19:56:31.000000000 +0000
++++ macosx/preferences-macosx.h        2018-11-04 16:43:54.000000000 +0000
+@@ -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  2018-11-18 19:57:31.000000000 +0000
++++ macosx/preferences-macosx.m        2018-11-04 16:47:58.000000000 +0000
+@@ -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];
++}
+--- rpc-machdep.c.orig 2018-11-02 19:15:19.000000000 +0000
++++ rpc-machdep.c      2018-11-18 19:54:52.000000000 +0000
+@@ -19,6 +19,9 @@
+  */
+ 
+ #include <string.h>
++#include <unistd.h>
++#include <stdlib.h>
++#include <pwd.h>
+ 
+ #include "rpcemu.h"
+ 
+@@ -26,7 +29,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  2018-11-18 19:54:25.000000000 +0000
++++ rpc-macosx.c       2018-11-02 20:04:55.000000000 +0000
+@@ -0,0 +1,89 @@
++/*
++  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);
++}
++
++
+--- qt5/rpc-qt5.cpp.orig       2018-11-02 19:15:19.000000000 +0000
++++ qt5/rpc-qt5.cpp    2018-11-04 16:45:30.000000000 +0000
+@@ -45,6 +45,15 @@
+ #include "ide.h"
+ #include "cdrom-iso.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"
+ 
+@@ -396,6 +405,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
+  *
+@@ -415,6 +441,22 @@
+       // 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::ControlModifier) != 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
+       // the configure window)
+@@ -435,7 +477,12 @@
+       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 HID manager for Caps Lock key events.
++      init_hid_manager();
++#endif /* Q_OS_MACOS */
++      
+       // Create Main Window
+       MainWindow main_window(*emulator);
+       pMainWin = &main_window;
+@@ -470,6 +517,15 @@
+ 
+       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);
+@@ -615,6 +671,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)
+  * 
+--- qt5/rpc-qt5.h.orig 2018-11-02 19:15:19.000000000 +0000
++++ qt5/rpc-qt5.h      2018-11-02 21:03:37.000000000 +0000
+@@ -49,7 +49,12 @@
+       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);
+       void mouse_press_signal(int buttons);
+@@ -76,6 +81,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);
+--- rpcemu.h.orig      2018-11-02 19:15:19.000000000 +0000
++++ rpcemu.h   2018-11-04 16:46:58.000000000 +0000
+@@ -170,6 +170,11 @@
+ 
+ /* These functions can optionally be overridden by a platform. If not
+    needed to be overridden, there is a generic version in rpc-machdep.c */
++
++#ifdef __APPLE__
++extern int rpcemu_set_datadir(const char *path);
++#endif
++
+ extern const char *rpcemu_get_datadir(void);
+ extern const char *rpcemu_get_log_path(void);
+ 
+--- ../../original/src/qt5/rpcemu.pro  2018-11-02 19:15:19.000000000 +0000
++++ qt5/rpcemu.pro     2018-11-18 20:15:49.000000000 +0000
+@@ -6,6 +6,9 @@
+ QT += core widgets gui multimedia
+ INCLUDEPATH += ../
+ 
++macx {
++        INCLUDEPATH += ../macosx
++}
+ 
+ HEADERS =     ../superio.h \
+               ../cdrom-iso.h \
+@@ -85,10 +88,31 @@
+                       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 +=      keyboard_macosx.c \
++                      ../hostfs-macosx.c \
++                      ../rpc-macosx.c \
++                      ../macosx/hid-macosx.m \
++                      ../macosx/events-macosx.m \
++                      ../macosx/preferences-macosx.m \
++                      choose_dialog.cpp
++
++      HEADERS +=      keyboard_macosx.h \
++                      ../macosx/hid-macosx.h \
++                      ../macosx/events-macosx.h \
++                      ../macosx/preferences-macosx.h \
++                      choose_dialog.h
++
++      ICON =          ../macosx/rpcemu.icns
+ }
+ 
+ # Place exes in top level directory
+@@ -133,4 +157,12 @@
+       TARGET = $$join(TARGET, , , -debug)
+ }
+ 
+-LIBS +=
++!macx {
++      LIBS +=
++}
++
++macx {
++      LIBS += -framework coreFoundation -framework IOKit -framework Foundation -framework Carbon
++
++      QMAKE_INFO_PLIST = ../macosx/Info.plist
++}
+--- qt5/settings.cpp.orig      2018-11-02 19:15:19.000000000 +0000
++++ qt5/settings.cpp   2018-11-02 20:42:30.000000000 +0000
+@@ -41,8 +41,10 @@
+       QByteArray ba;
+ 
+       snprintf(filename, sizeof(filename), "%srpc.cfg", rpcemu_get_datadir());
++      
++      rpclog("Loading configuration from '%s'.\n", filename);
+ 
+-      QSettings settings("rpc.cfg", QSettings::IniFormat);
++      QSettings settings(filename, QSettings::IniFormat);
+ 
+       /* Copy the contents of the configfile to the log */
+       QStringList keys = settings.childKeys();
+@@ -188,8 +190,10 @@
+       QString sText;
+ 
+       snprintf(filename, sizeof(filename), "%srpc.cfg", rpcemu_get_datadir());
++      
++      rpclog("Saving configuration to '%s'.\n", filename);
+ 
+-      QSettings settings("rpc.cfg", QSettings::IniFormat);
++      QSettings settings(filename, QSettings::IniFormat);
+ 
+       char s[256];
+ 



Home | Main Index | Thread Index | Old Index