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