Edenwaith Blog

EdenList 2.3.0 + 2.3.1 for iOS

9th July 2022 | EdenList

After reading an article about making a "satisfying checkbox", I was inspired to add a little extra fun and delight to EdenList by adding some haptic feedback when tapping on a list item (an effect I had once added to another now-defunct iOS app). I wanted to add something else to this patch release, so I added the large title display feature (introduced in iOS 11), which was very easy to implement, but it would require bumping up the minimum version of iOS and dropping support for iOS 10. Since any device that can run iOS 11 can also run iOS 12, I raised the minimum version to iOS 12. This was the initial work for what was expected to be version 2.2.1.

I tend to be cautious about bumping up the minimum version of an app, and requiring iOS 12 for just a patch release didn't seem worth it, so I decided to add a larger feature by being able to search through the main list of lists. I've found this to be useful if you have a lot of lists and want to quickly find a particular list or few (such as all "to do" lists). By adding this additional feature, it bumped up the app version to 2.3.0.

New features in EdenList 2.3.0 for iOS:

EdenList for Mac

A benefit of the newest Macs running on Apple Silicon (instead of Intel chips) is that they can also run iOS apps. Even though EdenList for Mac has not been updated in a dozen years, the iOS version can be downloaded and run on Apple Silicon Macs (M1 chips and beyond). I have done a preliminary test to verify that the iOS version does run on Apple Silicon. Not a perfect solution, but it does work.

An update to the original Mac version is slowly in the works to make it 64-bit and as a new type of Universal Binary (Intel/Apple Silicon instead of PPC/Intel) so it will work on modern Macs. The current 1.0 version will only run on versions of macOS from 10.4 through 10.14. This is proving to be more work than initially expected, primarily because the codebase hasn't been touched in a dozen years, and some of the code dates back to even 2003, so it definitely needs some clean up, improvements, and the removal of deprecated methods. I'm hoping to eventually rework EdenList into a more unified app (using Mac Catalyst, SwiftUI, or whatever) to provide the functionality across multiple platforms so the Mac version doesn't get left so woefully behind.

EdenList 2.3.1 for iOS

9 August 2022 update: I tend to only update EdenList on a yearly basis, but an astute user of EdenList notified me of a very long-standing (and cleverly hidden) bug in EdenList where if a list name contained a / in the name, then it would not properly save any data to the list. I fixed this issue so list names can no longer contain that slash. I also made a small UI update for the search bar for iOS 12.

New improvements in EdenList 2.3.1 for iOS:

Quest For Glory V for Mac OS 9 Installer Script

4th May 2022 | Programming

Twenty-one years ago I created an AppleScript to serve as an installer for Quest for Glory V on Mac OS 9. This was created because the original installer did not work under Mac OS 9 (but oddly enough, the installer that comes with the German version ("Drachenfeuer") of the game worked fine). This script was based off of advice I got from QFG5's lead programmer Eric Lengyel on which particular files need to be copied off of the install CD.

AppleScript is always kind of interesting to play with. Some things are quite elegant, whereas other things can be quite maddening in their implementation. The original script was far from a piece of beautiful coding prose, since it was likely produced by recording my actions using Script Editor. This updated version (1.1) of the script is a complete rewrite with several improvements and safe guards.

The installer script can be downloaded here.

The list of files which are copied over:

One feature I wanted to implement with this script was to be able to copy the QFG5 icon onto the main destination folder, but I have not found a way to do this in Mac OS 9. There is a way to do this in Mac OS X which requires "System Events", but unfortunately that is not available on Mac OS 9, so the function below won't work for this script. The following is an example code snippet that can copy the icon of a file and paste it onto a folder.


tell application "Finder"
	set sourcePath to file "Snow Leopard:Users:chad:Pictures:L-150.tif"
	set destPath to folder "Snow Leopard:Users:chad:Desktop:Foo:"

	my CopyOrPaste(sourcePath, "c")
	my CopyOrPaste(destPath, "v")
end tell

on CopyOrPaste(i, cv)
	tell application "Finder"
		activate
		open information window of i
	end tell
	tell application "System Events" to tell process "Finder" to tell window 1
		delay 0.25
		keystroke tab -- select icon button
		delay 0.25
		keystroke (cv & "w") using command down (* (copy or paste) + close window *)
	end tell
end CopyOrPaste

It's been five years since I've played Quest for Glory V, and this is the first of several QFGV-related projects I'm planning for this year. There's also a fan-made patch for the game to try out, in addition to setting up my ideal PowerMac system when I replay the game this year.

Changing Where Screenshots are Saved on a Mac

28th April 2022 | Programming

Since the classic days of Mac OS, the Desktop has been the traditional dumping ground for a variety of files and folders. When one takes a screenshot, the Desktop is the default location where to save it, but it is possible to change where screenshots are stored. A couple of quick commands will help clean up your Desktop so it isn't populated by quite so many screenshots.

Mac Screenshots

If you have macOS Mojave (10.14) or later on your Mac, the location can be set using the Mac's built in screenshot functionality.

  1. Press Command + Shift + 5
  2. Click on Options
  3. Pick one of the listed folders or choose Other Location. For my purposes, I selected the folder Screenshots in my Pictures folder (~/Pictures/Screenshot).

However, if you are not using macOS Mojave or later, you'll need to use the Terminal. The following commands sets the Mac's default location to a folder named Screenshots in the Pictures folder:

defaults write com.apple.screencapture location ~/Pictures/Screenshots
killall SystemUIServer

To read the default setting:

defaults read com.apple.screencapture location

If the location value has not been set yet, you'll see this response:

defaults read com.apple.screencapture location
2022-04-25 11:02:26.195 defaults[6668:108767] 
The domain/default pair of (com.apple.screencapture, location) does not exist

Or check all of the com.apple.screencapture settings.

% defaults read com.apple.screencapture
{
    "last-analytics-stamp" = "672074805.851666";
    "last-selection" =     {
        Height = 945;
        Width = 433;
        X = "-1201";
        Y = 118;
    };
    "last-selection-display" = 1;
    style = selection;
    video = 1;
}

After setting the location:

% defaults read com.apple.screencapture location
~/Pictures/Screenshots
% defaults read com.apple.screencapture
{
    "last-analytics-stamp" = "672074805.851666";
    "last-selection" =     {
        Height = 945;
        Width = 433;
        X = "-1201";
        Y = 118;
    };
    "last-selection-display" = 1;
    location = "~/Pictures/Screenshots";
    "location-last" = "~/Pictures/Screenshots";
    style = selection;
    target = file;
    video = 1;
}

iOS Simulator

If you are a developer who often saves screenshots of the iOS Simulator, it also has its own setting where to save the snapshots. Changing the location where the iOS Simulator saves screenshots is fairly similar to the approach to saving macOS screenshots when using the Terminal. The Terminal command to change the default location where the iOS Simulator saves screenshots:

defaults write com.apple.iphonesimulator "ScreenShotSaveLocation" -string "~/Pictures/Screenshots"
To verify, run:
defaults read com.apple.iphonesimulator "ScreenShotSaveLocation"

This should then reveal the location where the screenshot will be saved (e.g. ~/Pictures/Screenshots). Like the Mac example, if the location hasn't been set yet, you will get a response like this:

% defaults read com.apple.iphonesimulator "ScreenShotSaveLocation"
2022-04-26 19:52:49.189 defaults[20441:18169586] 
The domain/default pair of (com.apple.iphonesimulator, ScreenShotSaveLocation) does not exist

References

Games for the Logitech CyberMan

20th February 2022 | Games

The previous article about the Logitech CyberMan 3D Controller listed a number of games which worked with this unique peripheral. The CyberMan came with four demos: DOOM, Body Adventure, Shadowcaster, and Terminator: Rampage to showcase what its extended functionality could provide in games. This post will briefly cover these demos plus a couple other products such as the Mechwarrior II: The Clans demo and several SSI games.

Note: To ensure that the CyberMan is recognized, any other mouse drivers need to be disabled first. In my initial tests, the DOOM demo worked without any modifications, but none of the other products displayed any of the CyberMan's extra functionality. I ended up going into the autoexec.bat and commented out the code which loaded the CuteMouse driver, which left only the Logitech 6.31E driver to be loaded.

The autoexec.bat file configuration:

SET GALAXY=A220 I5 D1 K10 P530 T6
SET BLASTER=A220 I5 D1 T4
@ECHO OFF
rem C:\SOUND144\UTILITY\AZCAL.EXE
PROMPT $P$G
PATH C:\WINDOWS;C:\DOS;C:\
PATH %PATH%;C:\CYBERMAN
SET LMOUSE=C:\CYBERMAN
C:\CYBERMAN\MOUSE DUAL
SET MSINPUT=C:\MSINPUT
REM LH C:\MSINPUT\MOUSE\MOUSE.EXE /Q
REM LH C:\MOUSE\CTMOUSE.EXE
rem LH C:\PBTOOLS\VGAUTIL\CLMODE.EXE t640=60 t800=60 t1024=87 t1280=87
REM LH /L:0 C:\DOS\SMARTDRV.EXE 1024 512 /X
LH C:\PBTOOLS\VGAUTIL\TSRFONT.COM
LH C:\DOS\MSCDEX.EXE /D:MSCD001 /L:D
SET DIRCMD=/O
SET TEMP=C:\PBTOOLS\WINTEMP
SET WINPMT=[WINDOWS] $P$G
SET SOUND=C:\SOUND144
REM WIN
REM TYPE C:\NAV30\EXIT2.MSG

DOOM

Of all the titles which supported the Logitech CyberMan, this is the granddaddy of them all.

Without any additional configuration, the CyberMan worked right out of the box with this demo, proving that the device worked properly. It's possible to turn, strafe, move, and fire with the just the CyberMan, but the experience isn't great. I still prefer the standard keyboard+mouse combination, but I imagine with enough practice, one could get adept at using the CyberMan for the quick-twitch reactions in DOOM.

There's also a source file which indicates that Heretic (a game based off of the DOOM engine) also supported the CyberMan. I haven't tried this, but I would assume it plays in a similar manner to DOOM's controls. Further investigation will be needed to determine how many games based off of the DOOM engine also supported the CyberMan.


Quest for Glory: Shadows of Darkness (QFG4)

This is the game that started it all for me to check out the CyberMan. I ended up going down many paths in an attempt to get the CyberMan to work with a modern Mac and emulation software like DOSBox, ScummVM, or VirtualBox, but with no luck. I even bought a serial port card for a PC and installed that, but without proper drivers (the initial drivers were intended for DOS 6.22 and Windows 3.11), a modern version of Windows only identifies the CyberMan as a simple 3-button mouse. Once I obtained an era-appropriate computer (Packard Bell Legend 814CD), I was then able to get the CyberMan to work with these games.

As I mentioned in my comparison between the floppy disk and CD versions of this game, only the floppy disk version had support for the Logitech CyberMan, whereas a flyer included with the CD version explicitly mentions that it no longer supports that peripheral.

The CyberMan works decently with this game. The middle button seems to enable a stabbing/thrust attack instead of a swipe. I do not recall seeing this with normal mouse, so this is either something special with the CyberMan, or I had never tried this, or perhaps it's something unique with the floppy disk version. If batteries are inserted, there is some tactile feedback when the player is attacked. Against weaker enemies, such as the Vorpal Bunny or Badders, there is only a small buzz, but against bigger enemies, getting attacked will result in a larger vibration. It's more of a novelty feature than a truly useful addition, but the concept also extended to the N64 rumble pack and much more effectively in the eighth generation of game console controllers.

Here are some images of the CyberMan ad which came included in the purple box version of Quest for Glory: Shadows of Darkness.

I have a number of other topics planned about the floppy disk version of QFG4 and CyberMan, but those will be reserved for a potential future post.


Body Adventure

This is not a traditional game, but an educational product to learn about human anatomy. It was developed by Knowledge Adventure, which focused on educational titles. Knowledge Adventure would eventually be acquired by CUC International, the same company that acquired Sierra On-Line. Of all of the products I have tested, this worked out the best with the CyberMan by making use of the controller's 3D aspects to rotate a wireframe organ (e.g. stomach, heart, etc.) in all three dimensions. This demo was added with the CyberMan to replace the Mechwarrior II demo which was originally intended to be included.

According to the wiki page:

On November 25, 2014, five Knowledge Adventure titles were re-released digitally as DRM-Free exclusives on ZOOM-Platform.com through a partnership between JumpStart Games and the Jordan Freeman Group. The five titles included 3D Body Adventure, 3D Dinosaur Adventure, Dinosaur Adventure (Original), Space Adventure, and Undersea Adventure.


ShadowCaster

ShadowCaster was developed by Raven, the same company which also made the Heretic and Hexen games. The game engine was written by id Software's John Carmack, which was "a successor of the Wolfenstein 3D engine and a predecessor of the Doom engine." This game is a 3D hack-n-slash from 1993, an early adopter of the CyberMan.

Controls:

  • The standard left-right-forward-back movements just moves the cursor on the screen.
  • Up: jump
  • Pitch: pitch forward to move forward, pitch back for backwards movement.
  • Yaw: turn left or right.
  • Roll: strafe left or right.
  • Tactile vibration: The tactile vibration works when hit by enemies. There's only a single vibration setting in my initial testing, whereas other games will use different levels. The wiki page says it vibrates when hitting a wall, as well, but I never experienced this.

The Terminator: Rampage

I never could get this to run. When I tried to install the demo, it caused the computer to reboot. Even under DOSBox, it had the same effect. Perhaps if I ever find the full game, I will be able to properly test this game.

According to the wiki page, this game used the same game engine as The Elder Scrolls: Arena. However, I have not found any evidence that this Elder Scrolls game supported the CyberMan.


Mechwarrior II: The Clans (Demo 2)

Mechwarrior II: The Clans demo did not make it into the original demo bundle, so it was replaced with Body Adventure. From what I could gather, it sounded like early development of Mechwarrior 2 went through developer hell, and eventually evolved from being The Clans into 31st Century Combat.

I wasn't very good at the game, but the CyberMan did seem to work, but I was often resorting to using the keyboard to move around. As my mech was dying, the CyberMan did pulse lightly like a heartbeat, better than the strong whirring that some games have used for the tactile feature.

Since The Clans demo did not come with the CyberMan, I had to do some searching for it. From what I've read, there were two demos, but the second one seems to be the more playable version. I found several downloads for the demo, often coming in at 1.4MB, just the right size for a high density floppy disk, but I could never get any of those versions to install properly on my test machine (a Packard Bell Legend 814CD). I did eventually find a demo that came in closer to 3MB. I burned that to a CD and then installed it onto the Packard Bell, and that version worked. Unfortunately, I forgot where I exactly found that particular version of the demo after downloading several variants. Here are several links relating to the Mechwarrior II demo and related pages:

The supported controls as mentioned in the README file:

*** Cyberman ***
MechWarrior 2 fully supports the Cyberman by Logitech in the following
way:
Left Button   = Fires selected weapon
Right Button  = Change weapons
Pitch Forward = Increase throttle
Pitch Back    = Decrease throttle
Roll Left     = Left view
Roll Right    = Right view
Z-Up          = Up view (pull up)
Yaw Left      = Turn torso left
Yaw Right     = Turn torso right
Z-Down        = Center torso (push down)

Targeting the Mech is controlled by moving the Cyberman Up and Down 
within the X and Y axis square.


SSI Games

All three of these games (Menzoberranzan, Strahd's Possession, and Stone Prophet) were developed by DreamForge Intertainment and published by Strategic Simulations, Inc. who were notable for many of their computer games based off of AD&D properties, such Pools of Radiance and Champions of Krynn (both which were my introduction to AD&D). These three games used the same game engine, so they all share the same interface and controls.

When I initially tried using the CyberMan with these titles, it only acted like a standard mouse. I knew that the CyberMan's full suite of functionality did work, as proven with the DOOM demo. I later learned that I had to disable the CuteMouse DOS driver from the autoexec.bat file. Even though the Logitech 6.31E driver supports dual mice, having CuteMouse also loaded disabled the additional CyberMan functionality. Once I got the CyberMan enabled, I was able to test it out with the SSI games. The CyberMan works, but the additional controls aren't particularly groundbreaking in what they add to the game. There were still areas in the game where I needed to press the ESC key to skip a scene, so the keyboard was still required at times.

Controls:

  • Yaw: Turn the party left or right
  • Roll: Slide party left or right
  • Pitch: Move party forward or backward
  • Y: Move mouse up and down the screen
  • X: Move mouse left and right on the screen
  • Z: Not used
  • Pitch and Yaw can be used in conjunction with each other so that you can move forward and turn at the same time.

Side notes:

It's nice that these games offer an option with a party already created to quickly dive into the game (versus the traditional method of endlessly curating your characters during the creation process). I remember spending several hours just setting up the party in Champions of Krynn, and this apparently hasn't changed much with modern RPGs, so the quick start approach is handy for one who just wants to play immediately.

I kept getting a generic "Unable to open resource file" when trying to start any of these games, until I tried Stone Prophet which had a slightly more useful error message that mentioned it could not open the file 'RES0/D:\RES0', which indicated I forgot to put in the game's CD. Sierra games tended to be a little more informative when the CD was required, but I had encountered a variety of configuration issues with the games reviewed in this article, so this looked like just one more error.


Conclusion

So how did the CyberMan fare with these products? The CyberMan was a bold idea, but its implementation was not the best, and it did not gain much support aside from a handful of games in the mid-1990s. Some of the games seemed to offer minimal support, but others made better use of what the CyberMan offered. Not all games bothered with the tactile feedback, some kept it very basic, and others were a little more creative. With the modern PS5 controller, the tactile feedback of the buttons is a feature which adds additional interactivity with games, and this shows that Logitech was ahead of many competitors with this idea. However, I've found that moving the cursor around with the CyberMan feels fairly stiff and lacks the precision that a standard mouse would normally provide, so it does not do well as a general input device, especially when used in Windows 3.1.

Edenwaith 2021 in Review

1st January 2022 | Edenwaith

This past year marked the 20th anniversary of the Edenwaith website, which has diligently served as a repository for my software projects and random technical experiments. The first decade was dedicated to creating new software for the burgeoning Mac OS X market. Most of those initial apps have been retired now, but a couple of mainstays like Permanent Eraser, EdenList, and even EdenMath are still around.

There was not much in the way of traditional software development in 2021, primarily due to my job being quite busy for the first half of the year, so there was less time and energy to lend to side projects. However, there were a couple of software updates with AGI Studio and EdenList. After several years of contemplation, I have finally retired 33 RPM. Work on Permanent Eraser 3.0 has begun in earnest, but there is no defined timeframe for when it will be released. I did manage to write and complete my first short story in seven years (in addition to revising my current novel which I hope to have completed the second draft in 2022) with the Space Quest-themed Another Day, Another Buckazoid. There's also been some interesting research done with topics of color theory, serial drivers, and fonts, all which I hope to delve into further this year.

Software Updates

Prominent Blog Posts

While there were not many software updates, fourteen blog posts were written, and here are some of the more interesting ones from 2021. The article about the Logitech CyberMan had been in the works for quite awhile and became quite the deep dive into how serial mice work and trying to get it to work with modern computers.

2022

This year will likely focus on more game-related topics, and less on traditional software. However, I do now have an M1-equipped MacBook Pro and have more reason to update some Mac software to support the new Apple Silicon chips. The following are a number of topics I intend on researching and/or working on:

Logitech CyberMan 3D Controller

27th December 2021 | Games

One of the things which made the floppy disk version of Quest for Glory: Shadows of Darkness (QFG4) unique from its CD successor is that it supported the original Logitech CyberMan 3D Controller. The CyberMan was an interesting peripheral which acts like a mouse which can (somewhat) move in 3D space (X-Y-Z coordinates in addition to pitch, yaw, and roll). It was only supported on a handful of games from the mid-90s, such as DOOM, Hexen, and Quest for Glory: Shadows of Darkness. If batteries are added, the CyberMan also provides tactile feedback. This was an early attempt of creating a 6-axis controller, but it wouldn't be effectively implemented until the Playstation 3's SIXAXIS controller.

The following products supported the CyberMan:

This list is not exhaustive, and as do more research, I discover additional games which supported (or claim to have supported) the CyberMan. The two Ravenloft games and Menzoberranzan are based off of the same game engine, which explains why the CyberMan works with all three. Only the floppy disk version of Quest for Glory 4 worked with the CyberMan, as the CD release explicitly mentions that it does not support this controller.

The CyberMan I purchased off eBay came in near pristine condition. The box was in excellent condition, in addition to the manuals, floppy disks, and the controller. The only true evidence that this CyberMan had been used is because batteries had been left inside...and after an inditerminate amount of time, the batteries had leaked. Sigh. Fortunately, after I got everything set up and working, I was able to confirm that the tactile feedback does still work when I tested it in Quest for Glory: Shadows of Darkness.

USB to Serial

The first order of business was to ensure that the device actually worked. The first iteration of the CyberMan was released in 1993, several years before USB became commonplace for computer peripherals. Instead, the CyberMan uses an RS-232 9-pin serial connector, which is an obstacle in trying to interact with modern computers which generally don't have serial ports these days.

Since I initially started the testing on a Mac, I purchased the following cable from Micro Center: QVS USB 2.0 (Type-A) Male to DB-9 RS-232 Serial Male Adapter Cable 6 ft. - Black. To get more details about the cable, I used the Mac's System Profiler which identified the cable as:

USB-Serial Controller:

  Product ID:	0x2303
  Vendor ID:	0x067b  (Prolific Technology, Inc.)
  Version:	3.00
  Speed:	Up to 12 Mb/sec
  Manufacturer:	Prolific Technology Inc.
  Location ID:	0x14100000 / 28
  Current Available (mA):	500
  Current Required (mA):	100
  Extra Operating Current (mA):	0

One note about the supplied power is what USB and traditional serial ports could offer, which generally was enough to power a standard mouse or joystick, but to enable the tactile vibration, batteries were needed to give the extra power.

For some delightful technical goodness, the command line gives a few extra details, such as that the Prolific PL2303 driver is used for this adapter.

% ls /dev/tty.usb*
/dev/tty.usbserial

% kextstat | grep prolific
  188    0 0xffffff7f83ae3000 0x7000     0x7000     com.prolific.driver.PL2303 (1.6.3) 
  E55010C7-C7DD-36D8-AE20-E050E3AFCB2B <61 52 6 5 3>
  
% ioreg -c IOSerialBSDClient | grep usb
      | |                 "IOTTYBaseName" = "usbserial"
      | |                 "IOCalloutDevice" = "/dev/cu.usbserial"
      | |                 "IODialinDevice" = "/dev/tty.usbserial"
      | |                 "IOTTYDevice" = "usbserial"

Another graphical utility to get additional information about the connected adapter is to use the development tool IORegistryExplorer. This app used to be provided with Xcode by default, but now it is a separate download as part of the Additional Tools for Xcode package which is downloaded from Apple's developer portal.

It had been about ten years since I had worked with a USB to serial converter, but I did find some old notes which reminded me I had to look for a PL2303 driver to get the cable to work properly with macOS. Unfortunately, the old driver I had used previously was no longer freely available via its SourceForge page. Fortunately, I found that the manufacturer of my cable did offer an up-to-date driver. Downloading and installing the appropriate driver worked perfectly under macOS Mojave. Note: Extra security measures in more modern versions of macOS (Big Sur and later) seem to have issues with some kernel extensions, so I have not tried this adapter with macOS Big Sur or Monterey, yet.

Terminal Output

With the PL2303 driver now installed, I next turned to using my favorite serial program Parley from Buttered Cat Software. After setting some options (4800 7-N-1) and connecting the CyberMan (seen as a usbserial device to Parley), I was able to see some output from the device to confirm that it did work.

From Parley (4800 baudrate with 7 data bits : 4800 7-N-1)

**** Port Open
ø€øø€øxøxxøxøxøxøxøxø€xøøøø€xøøøøxøxøxøxøxøxø€x€øøxøxxøxøxø
øøøx€xøøø€xx€€€€€€€€€€ø€xøxø€€x€€xøøø€€øx€x€x€
x€x€xx€xøx€x€€xøøxøxøøxøxøøx€x€x€x€x€x€x€x€x€x€
x€x€x€x€x€x€x€x€x€x€x€x€xø€xøxøø€€xxøøxøxøøøøxøx€ø€ø
øø€øø€x€øø€x€€x€€€ø€€x€øøø€øøø€€x€€øø

From what I've read, most serial mice used a 1200 baud, 7 bit setting (1200-7-N-1), which is unfortunate since the lowest baud setting in Parley is 4800. Another option was to use command line tools.

screen /dev/cu.usbserial 1200

��������������������������������������������
�����������������������������������������

ͳǟ�Ā�Ā����������������������������������������
�������������������������ô�ß�ñ�����������͸���
ý�ñ�ý�ú�÷�÷�ý�ý�ý�ý�ñ�å�ô���ý���������ϴ�Ϯ�̀�̀�̀�̀�̀�̀�
ý���������������������ô�Ü�Ù�Á�ý����������ý�Ѐ���ë
��������������������������������������������
Ü�ý�ý�ô�Ó�»�¬� �£�¯�¾�ë���������������������������
�������Ѐ�Ѓ�Ѓ����������������������������������
�������������������̀�̀�̀�̀�̀�����̉�̌�̆�̆�̆�̆�̃�̃�̃�
̃�̆�̃�̀�̀�̀�̀�̀�̀�̀�̀�̀�̀�̀�̀�̀�Ā�����ă�������������
������������͸���͸���̘�����������ô��²��¯�©�ô�ý��
�������������

Another option I tried was to communicate with a serial device in DOS in either DOSBox or VirtualBox. Unfortunately, I was never able to get any of these emulated versions of DOS running on my Mac (yet) to see the serial device, but here are some different options I tried.

MODE COM1:1200,N,7,1,P

https://kb.iu.edu/d/afao
> kermit
set port 1
set baud 9600
connect

None of the data from Parley or screen was very intelligible, but it did prove that the device worked to some degree. I switched over to CoolTerm, which does have the option to set the baudrate all the way down to 300 baud. But for the ideal configuration, I set the options for this connection to 1200 baud rate at 7 data bits and 1 stop bit with no parity (1200 7-N-1). The data from the CyberMan now looked like this:

Õ≥…£à»Äæ¿ÄÉ¿Ä܇ÄÄ¿ÄćÄÄ¿ÄćÄÄ¿ÄÄ√ΩÄ√ΩÄ–ÄÄ¿ÄÄ√ΩÄ√ΩÄ¿ÄĆ¿ÄɆ¿ÄÄÄ√ΩÄ–ÄÄ¿
ÄÄ√ΩÄ√¢í¬ó≥¬àõ√üĬóâ¿ÄÉ¿ÄÜ√∑ï√ΩÉÃå®ÃòäðÅúÑÕÖçÕ∏Ñ¿ºÄÕ∏¥¡∏Ä¡îÄ¿òòƒòëƒò∏ƒò؃ò∏¿Ä
í«¥ó∆àã√¥Ä¬à°¬æĬàÉ√ÅÄŒàΩ¬£Äœüñœ®¥œ®ÑÀ®≤œ®çÃ屡¨Ä¿ºÄ¿ºÄ¡∏Ä¿ÉÄ¡∏Ä¡¶Ä¿™Ä¿™Ä¡ÖÄ¿ïÄ
¿òÄ¿òÄ¿òÄ¿òÄ¿òÄ¿òÄ¿òÄ¿òÄ¿òÄ¿òÄ¿òÄ√ΩÄ√äĬµÄ¬óÜ∆àé¬ØÄ«äà¿™ò¡¶Ü¿ßÄ¿èÄ¿ÉÄ¿Äõ√Ωõ√Ωπ√
∑ò√∑Ü¿Äò¿Äò¿Äò√Ωò¿Äò¿Äò¿Äò¿Äò¿Äò¿Äò¿ÄòÃÄ∫»Ä©»ÄµÃÄÅ»Ä∏ÃÄ¥ÃÄΩœΩΩ¬¶ÄŒà∫¬àĬØÄ√¥Ä√Ω
Ä√ΩÄ√∑Ä√®Ä√®Äœ®Ω√®Ä√®Ä√®Ä√®Ä√®Ä¡ÇÄ¡ùÄ¡∏Ä¡ãÄ¡∏Ä¡∏Ä¿ïÄ¿πÄ¿ÉÄ√∑Ĭ†Ä¬àĬ¶Ä à†¬∏Ä óàÃ
ÄÑÀ´àÃÄüÃÉ®Õ֮ú®Õé®Õ∏®Õ∏®Õ∏®¿øÄ≈îã¿ò™¿òøƒòÖƒò∏«¢£∆éù¬àø¬î⬪ĬæĬ¨ÄŒ¨´œáñœ¥úœΩú
»ÉØÃò∑Õó∑ÕîçÃ≥Ω¿ÄÜ¿ÄÉ¿Äâ¿ÄÉ√±Ä√¥Ä√ΩÄÃÄ∫ÃÄäÃâ±ÃÜ∫ÃÄ∫œ∑ΩÃÄΩ√ΩÄ

CoolTerm has an option to convert the garbled ASCII output into hex codes, which is far more manageable to dissect the data coming from various commands sent by the CyberMan. All further data mentioned in this article will be either hex values (C0) or binary (1100 0000).

Serial Mouse Protocol

Before delving too deeply into CyberMan's technical details, let's inspect how the Microsoft serial mouse protocol works, which is the basis for how Logitech mice also worked. These details are from the original archived article by Tomi Engdahl <then@delta.hut.fi>, which is the basis for numerous other documents found on the internet.

Packet Format

        D7      D6      D5      D4      D3      D2      D1      D0
Byte 1  X       1       LB      RB      Y7      Y6      X7      X6
Byte 2  X       0       X5      X4      X3      X2      X1      X0      
Byte 3  X       0       Y5      Y4      Y3      Y2      Y1      Y0

LB is the state of the left button (1 means pressed down)
RB is the state of the right button (1 means pressed down)
X7-X0 movement in X direction since last packet (signed byte)
Y7-Y0 movement in Y direction since last packet (signed byte)

               1st byte        2nd byte         3rd byte
          ================  ===============  ================
           - 1 ? ? Y Y X X  - 0 X X X X X X  - 0 Y Y Y Y Y Y
          ================  ===============  ================
               | | \ / \ /      \---------/      \---------/
               | |  |   |            |                |
               | |  |   \----\       |                |
               | |  \--------|-------|--------\       |
               | |          / \ /---------\  / \ /---------\
               | |         ================ =================
               | |          0 0 0 0 0 0 0 0  0 0 0 0 0 0 0 0
 Left Button --/ |         ================ =================
Right Button ----/            X increment      Y increment

Each time the mouse state changes (e.g. the mouse moves or buttons are pressed/released), a data packet is sent to the host. Each data packet is comprised of three 7-bit bytes. In the data culled from the CyberMan, each byte is made up of 8 bits, but the leading bit is always set to 1, but it is ignored, so an "empty" byte will still be 0x80 (1000 0000 in binary). An example data packet of the left mouse button being pressed would be E0 80 80 .

Note: The bit marked with X is 0 if the mouse received with 7 databits and 2 stop bits format. It is also possible to use 8 databits and 1 stop bit format for receiving. In this case, X gets the value 1. The safest thing to get everything working is to use 7 databits and 1 stopbit when receiving mouse information (and if you are making a mouse then send out 7 databits and 2 stop bits).

The byte marked with 1 is sent first, then the others. The bit D6 in the first byte is used for synchronizing the software to mouse packets if it goes out of sync.

According to this description of the PC mouse by Petr Simandl, D7 and D6 of Byte 1 will always be 1. This corresponds to the CyberMan output where the first byte in the data packet is at least 0xC0 (1100 0000). Having the D6 bit set to 1 indicates the start of a new data packet, so any subsequent byte in the packet will not have the D6 bit set.

Examples of the first byte of a data packet, dependent upon various button states:

No buttons down:   0XC0 = 1100 0000
Left button down:  0xE0 = 1110 0000
Right button down: 0xD0 = 1101 0000

From the Serial Mouse Detection documentation:

The following is the data report format. Data is transmitted serially (LSB first) in the form of seven bit bytes. X and Y data are incremental movements with Y movement considered positive to the south. (For the PS/2 data format, Y movement was considered positive to the north). The data packet format is three or four bytes long. The fourth byte is transmitted in either of the following cases a. The middle button is depressed b. A report is sent while the middle button is depressed c. The middle button is released

Additional details from Logitech Logimouse C7 Firmware Rev 3.0 Jan86 corroborates the original details on the serial mouse data format:

LOGIMOUSE C7 

12.2 Microsoft. Compatible Data Format 

In the Microsoft Compatible Format, data is transferred in 
the form of seven bit bytes. Each report consists of three 
bytes. X and Y are relative movements. In the Microsoft 
Compatible Format, Y movement is positive to the south and 
negative to the north. 

The command to select the Microsoft Compatible Format is 'V' 
(56H) . 

6  5  4  3  2  1  0  Bit number 
1  L  R  Y7 Y6 X7 X6 Byte 1 
0  X5 X4 X3 X2 XI X0 Byte 2 
0  Y5 Y4 Y3 Y2 Yl Y0 Byte 3 

L,R   = Key data (Left, Right key) 1 = key depressed 
X0-X7 = X distance 8 bit two's complement value -128 to +127 
Y0-Y7 = Y distance 8 bit two's complement value -128 to +127 
        Positive = South 

If LOGIMOUSE C7 is set by jumpers to the Microsoft 
Compatible Format, at power-up it will send one character 
'M' (4DH) . No character is sent if the Microsoft Compatible 
Format is selected with a command. 

LOGIMOUSE C7-M always sends 'M' (4DH) when the host toggles 
the RTS signal line. In response to a Microsoft driver reset 
(i.e. toggling RTS), LOGIMOUSE C7-M sets up its operating 
parameters to Microsoft compatible format, Incremental 
Stream, continuous reports, 1200 baud, regardless of the 
current settings or the jumpers. 

3 Button Logitech Protocol Extension

These three different pages detail how Logitech extended the Microsoft mouse protocol to support a third mouse button.

Logitech uses this same protocol in their mice (for example Logitech Pilot mouse and others). The original protocol supports only two buttons, but Logitech has added a third button to some of their mouse models. To make this possible Logitech has made one extension to the protocol.

Logitech extended the 2 button mouse protocol to support 3 button mice by adding a 4th byte when the middle button is pressed (and the first packet after it is released). If a 4th byte is encountered (i.e., an extra byte with D6 set to 0) then D5 of that byte (0x20 or 32 in decimal) indicates the status of the middle mouse button.

Logitech serial 3-button mice use a different extension of the Microsoft protocol: when the middle button is up, the above 3-byte packet is sent. When the middle button is down a 4-byte packet is sent, where the 4th byte has value 0x20 (or at least has the 0x20 bit set). In particular, a press of the middle button is reported as 0,0,0,0x20 when no other buttons are down.

I have not seen any documentation about the exact documents, but here is what I have found out: The information of the third button state is sent using one extra byte which is send after the normal packet when needed. Value 32 (dec) is sent every time when the center button is pressed down. It is also sent every time with the data packet when center button is kept down and the mouse data packet is sent for other reasons. When the center button is released, the mouse sends the normal data packet followed by data byte which has value 0 (dec).

This looks very much in line with what is seen in the section below when pressing the middle button sends four bytes (e.g. C0 80 80 A0), whereas any other action sends three bytes. This is an interesting method to implement the middle button by adding an additional byte. According to the documentation above, the D5 bit is set, so the byte will have the value of 0x20 (0010 0000). Since each of these bytes in the data packet have the first bit always set to 1, the returned value is A0 (1010 0000, which is 0x80 + 0x20). The computer determines when a new data packet comes in by checking if the D6 bit is set to 1. Other mouse protocols ended up using more bytes per data packet to deliver additional data.

CyberMan Hex Codes

The CyberMan's controls are more akin to a joystick than a typical mouse by supporting the movement of the controller for yaw, roll, pitch, and also along the Z-axis. The following are the responses from the CyberMan when each of its controls are activated.

Buttons:
Left button down:	E0 80 80
Left button up:		C0 80 80
Middle button down:	C0 80 80 A0
Middle button up:	C0 80 80 80
Right button down:	D0 80 80
Right button up:	C0 80 80

L+R buttons down: 	F0 80 80 
L+M buttons down: 	E0 80 80 A0
M+R buttons down: 	D0 80 80 A0
L+M+R buttons down:	F0 80 80 A0

X-Y Axes:
Left:			C3 A8 80 
Right:			C0 98 80 
Forward:		CC 80 A8
Backwards:		C0 80 98 

Forward-Left:		CF A8 A8
Forward-Right:		CC 98 A8
Backwards-Left:		C3 A8 98
Backwards-Right:	C0 98 98

Z-Axis:
Up:			CC 80 BD 
Down:			C3 BD 80

Yaw:
Rotating-Left:		C0 86 80 
Rotating-Right:		C3 BD 80 (Note: Same value as Down, one of these are likely incorrect)

Roll:
Right:			C3 BD 80
Left:			C0 8F 80

Pitch:
Forward:		C0 80 8F
Back:			CC 80 BD 

These are the approximate values I could get from the output. The data for the three buttons and moving along the X-Y plane are consistent, but the other data seems far more chaotic.

The first byte of each data packet is at least C0 (1100 0000), indicated by the D6 (second from left) bit being set. Most of the data packets are three bytes, unless the middle button is pressed, which then increases each packet to four bytes.

C0 = 1100 0000 : No buttons down 
E0 = 1110 0000 : Left mouse button down
D0 = 1101 0000 : Right mouse button down
F0 = 1111 0000 : Both left and right buttons down

As shown in the Packet Format diagram, if the D5 bit (third from the left) is set in the first byte, it indicates that the left button is pressed. If the D4 bit (fourth from the left) is set, then the right mouse button is pressed. If the first byte is F0, then it indicates that both the left and right buttons are pressed, so the D5 and D4 bits are set.

As mentioned in the 3 Button Logitech Protocol Extension section, the middle button is indicated by the addition of a fourth byte. When the middle button is pressed, the A0 is the extra byte. When the middle button is released, an extra 80 is available to indicate that the middle button is no longer active.

Middle button down:	C0 80 80 A0
Middle button up:	C0 80 80 80

This fourth byte will only appear when the middle button is active or the middle button has just been released. This adds a little extra complexity to the mouse driver, because it cannot always assume that each data packet is going to be exactly three bytes in length. Instead, the driver needs to check if the D6 bit on a byte is set to determine the start of a new data packet. Notice that the fourth byte is given a value of A0, which is 1010 0000 in binary, and the D6 bit is 0. When the middle button is released, the fourth byte returns to an "empty" state of 80.

Calculating the X and Y positions is interesting by the way it takes two bits from the first byte, and then combines it with the last six bits of either the third or fourth bits.

Left:		C3 A8 80 (11000011 10101000 10000000)
Right:		C0 98 80 (11000000 10011000 10000000)
Forward:	CC 80 A8 (11001100 10000000 10101000)
Backwards:	C0 80 98 (11000000 10000000 10011000)

The X (left and right) coordinates take the last two bits (D1 and D0) from the first byte and combine it with the last six bits from the second byte. For the Y coordinates, it takes the third and fourth (D3 and D2) bits from the first byte and then merges those with the last six bits of the third byte.

Left:      11101000 => E8
Right:     00011000 => 18
Forward:   11101000 => E8
Backwards: 00011000 => 18

It's interesting to see when the max values of each direction are constructed, that both Left and Forward equal E8, and Right and Backwards equal 18.

Now that we have covered the basic operations, let's combine them and inspect the output. Moving the CyberMan Forward and Left will return CF A8 A8, and holding down the left mouse button at the same time will return EF A8 A8.

Forward-Left:		CF A8 A8 (CC 80 A8 | C3 A8 80 => CF A8 A8)
Forward-Right:		CC 98 A8 (CC 80 A8 | C0 98 80 => CC 98 A8)
Backwards-Left:		C3 A8 98 (C0 80 98 | C3 A8 80 => C3 A8 98)
Backwards-Right:	C0 98 98 (C0 80 98 | C0 98 80 => C0 98 98)

There is a computational beauty of how the various input values are calculated, which uses the bitwise OR operator (indicated by the vertical pipe character: |). If both the left and right mouse buttons are down at the same time, then the resulting data packet is F0 80 80, which is calculated by E0 | D0 => F0.

   1110 0000 (E0)
OR 1101 0000 (D0)
------------
   1111 0000 (F0)

If the mouse is set to Backwards-Left and the middle button is pressed down, the data packet C3 A8 98 A0 is sent. Once the middle button is released (but the mouse isn't moved from its position), the data packet C3 A8 98 80 is sent once and then the standard three byte data packet C3 A8 98 is afterwards.

It appears that the X-Y values for the 2nd and 3rd bytes have a range between A8 (1010 1000) and 98 (1001 1000), which leaves room for the CyberMan to handle the extra functionality for the Z-axis, yaw, pitch, and roll. The values I saw in the terminal were not as consistent, but it appears that any extra values were either higher (BD) or lower (8F).

This covers traditional mouse functionality, but what set the CyberMan apart from its traditional counterparts is its ability to work in three dimensions. Additional details per the Logitech CyberMan's manual:

Looking at how the Z-axis is handled, it initially looks like it is moving Forward (CC) when going Up, but the third byte is BD, which is larger than a normal Forward value can achieve. There is a similar approach when pressing the mouse Down. The first byte is C3, which looks like the mouse is moving left, but the second byte is BD, which indicates it is not within the standard range of moving along the X-axis. When releasing from the Up position, a data packet of C0 80 83 is sent.

Up:	CC 80 BD 
Down:	C3 BD 80 

The Yaw, Roll, and Pitch follow similar approaches of communication by using a mixture of values between the three bytes to indicate what type of data is being sent. Yaw and Roll make use of the second byte, whereas Pitch uses the third byte. The data I'm seeing is inconsistent, so take some of these examples with a grain of salt, and this will require further testing to get more reliable results, or my CyberMan may not be 100% functional.

Conclusion

The Logitech CyberMan 3D Controller was an interesting PC peripheral that was a little ahead of its time. It was followed up by the Logitech CyberMan 2 a couple years later, which sported a different form factor, likely to address the ergonomic difficulties the original possessed, but the second model didn't set the world on fire, either.

This has been an interesting experiment to delve into the ancient world of serial mice and odd peripherals, one which I intend on further pursuing to see if I can get the CyberMan to successfully work on modern computers and try out some of the supported games.

References

Removing Mac Files From Quarantine

9th December 2021 | Programming

When setting up a new Mac running macOS Big Sur, I tried to open a BBEdit Clipping that was transferred via AirDrop, which resulted in this error:

"Current Date" cannot be opened because it is from an unidentified developer.
macOS cannot verify that this app is free from malware.
Durenor sent you this file on November 17, 2021.

Then that error was followed by another delightful error:

The application "BBEdit.app" can't be opened.
-128

Interestingly enough, it was possible to open these Clippings from the command line text editor pico. When I installed the BBEdit command line tools, I was also able to open the Clipping with the command bbedit Current\ Date. But trying to open the file from Finder did not work.

Right-clicking and selecting Open should work as a work around, similar to how to open unidentified apps. I figured this was an issue with the files being quarantined, so I looked for the command line solution.

How to check if a file is quarantined using the Terminal by using the xattr command:

Clippings % xattr Current\ Date 
com.apple.LaunchServices.OpenWith
com.apple.TextEncoding
com.apple.quarantine

To remove the quarantine tag, use the xattr -d com.apple.quarantine command on the quarantined file.

% xattr -d com.apple.quarantine Current\ Date 

Then check again to verify that com.apple.quarantine has been removed.

% xattr Current\ Date                      
com.apple.LaunchServices.OpenWith
com.apple.TextEncoding
com.apple.lastuseddate#PS

References

Differences Between the Floppy Disk and CD Versions of Quest for Glory 4

19th October 2021 | Games

It's October, which means it is time to play some Quest for Glory: Shadows of Darkness! I've played this game numerous times over the past 25 years, and I still occasionally find new things, but this year I decided something different by playing both the floppy disk and CD versions of the game. This game was initially released in 1993, but it was rushed out to make the holiday season and was notoriously buggy. The CD version of the game followed a year later which included voice acting, in addition to many bug fixes.

I played through both the floppy disk and CD versions to look for differences between the two, in addition to various bugs (what is this — a bug hunt?). I was able to get through most of the floppy disk version, but when I returned to the Dark One's cave, it got stuck and acted like the hero kept trying to leave the cave instead of entering it. Unfortunate, but close enough for horseshoes and hand grenades to compare these two versions of Quest for Glory 4.

Floppy

CD

Other

Going through these two versions of this game shows the importance of proper testing and how much effort goes into creating a game. While the floppy disk version was mostly playable until the end, it was interesting to come across areas which were buggy, missing animations, or were changed for the CD version. The update did clean up a number of issues, but even still there were a number of misses (such as missing or incorrect voice tracks). What wasn't expected several years after the game's release would be speed-related bugs that would plague many of the early 90s Sierra games. Fortunately modern utilities like DOSBox, ScummVM, and the NewRisingSun patch help immensely to curtail the worst of the issues and make this game a joy to experience over and over again.

Quest for Glory 4 Wraith Freezing Bug

18th October 2021 | Games

Quest for Glory: Shadows of Darkness (QFG4) is no stranger to bugs. Errors 47 and 52 drive more fear into gamers' hearts than all of the undead creatures featured in this horror-infused game. The original version of the game had been rushed out far too early to meet the impending holiday deadline, which resulted in a very buggy game. I recently tried playing through the floppy disk version of QFG4 and got most of the way through until the game got stuck near the end upon returning to the Dark One's cave. The CD version of the game fixed a number of bugs, added some polish, and included the excellent voice acting, which makes the game feel barren without it.

Yet, even with these improvements, there remained a number of bugs, some which were only unearthed later as computers got faster. Many Sierra games from the early 90s were similarly afflicted by speed-related issues, which required utilities like Turbo or MoSlo to try and slow down the computer so the game would run properly. The indispensable NewRisingSun patch has been an incredible blessing to fix many of the most egregious issues in QFG4. When I first played this game, I encountered both Errors 47 and 52 within the first hour of game play. Fortunately, with the NRS patch, I haven't seen either of these errors for many years when I play the game under DOSBox.

Unfortunately, I have come across a random bug (that others have also noticed) when after fighting a wraith at its burial mound, the game would essentially lock up. One can see the drop down menu, but pretty much everything except the Settings menu is greyed out. In the margins of one of my maps I had scribbled down this note about this issue:

"When fighting wraiths, wait until it appears before fighting it. Otherwise, the game will freeze after the fight."

I found that this issue was somewhat random at times. Sometimes I would go into a screen with a wraith and have no problem triggering the wraith to appear. Other times the wraith would be a no-show. The key is to look for the red dot floating around the burial mound. If you don't see it immediately, then leave the screen and return at another time. But once the floating red dot is visible, approach the mound and wait until the wraith fully appears before initiating combat.

I never encountered this particular problem with the floppy disk version of QFG4, so this particular bug might be a side effect of the NRS patch. Another post recommends removing the wraith patch files 53.SCR and 53.HEP from the PATCHES folder. These patches fix the wraith draining health bug, but might also be causing this occasional issue, as well. A temporary work around is remove the two 53 files, fight the five wraiths at their burial mounds, then replace the two patch files. Otherwise, just return to the mounds if the wraith doesn't appear immediately.

And as always, check out Sierra Help for numerous other tips and patches to resolve various issues in Quest for Glory 4 and other Sierra games.

Implementing NSServices for macOS

18th September 2021 | Programming

As far back as 2005, I was looking at adding support for NSServices to Permanent Eraser. However, due to poor design decisions, it was not possible to implement them due to timing issues. With Snow Leopard's improved support for NSServices, I created a plug-in service to allow a user to right-click on a file and be able to erase it with Permanent Eraser.

With the intended successor to Permanent Eraser 2, I have been experimenting with adding support for NSServices. NSServices have been around for a long time, so the available information is somewhat jumbled, especially with some additions which were added with Mac OS X 10.5.

Info.plist

The first step is to add NSServices key-value pair the Info.plist. Below is a simple example to configure a single service.


<key>NSServices</key>
<array>  
	<dict>
		<key>NSMenuItem</key>
		<dict>
			<key>default</key>
			<string>Service Menu Title</string>
		</dict>
		<key>NSMessage</key>
		<string>doSomeStuffMethodName</string>
		<key>NSPortName</key>
		<string>SomeApp</string>
		<key>NSRequiredContext</key>
		<dict/>
		<key>NSSendFileTypes</key>
		<array>
			<string>public.item</string>
		</array>
	</dict>
</array>

Here is another example with some excellent comments on what the various keys represent.


<key>NSServices</key>
<array>        
    <dict>
        <key>NSMenuItem</key>
        <dict>
            <key>default</key>
            <string>Folder Handling Demo</string>
        </dict>
        <key>NSMessage</key>
        <string>handleServices</string> <!-- This specifies the selector -->
        <key>NSPortName</key>
        <string>Tmp</string>       <!-- This is the name of the app -->

        <!-- Here we're limiting where the service will appear. -->
        <key>NSRequiredContext</key>
        <dict>
            <key>NSTextContent</key>
            <string>FilePath</string>
        </dict>
        <!-- This service is only really useful from the Finder. So
         we want the finder only to send us the URI "public.directory"
         which *will* include packages (on the off-chance you want to
         see the full package directory name) -->
        <key>NSSendFileTypes</key>
        <array>
            <!-- Check out "System-Declared Uniform Type Identifiers"
             in the Apple documentation for the various UTI types.
             In this example, all we want is a directory, which is
             a super-type to other types (e.g. public.folder) -->
            <string>public.folder</string>
        </array>
    </dict>
</array>

For Permanent Eraser, I have a more complex version set up.


<key>NSServices</key>
<array>
	<dict>
		<key>NSKeyEquivalent</key>
		<dict>
			<key>default</key>
			<string>E</string>
		</dict>
		<key>NSMenuItem</key>
		<dict>
			<key>default</key>
			<string>Erase File</string>
		</dict>
		<key>NSMessage</key>
		<string>eraseService</string>
		<key>NSPortName</key>
		<string>Permanent Eraser</string>
		<key>NSRequiredContext</key>
		<dict>
			<key>NSTextContent</key>
			<array>
				<string>FilePath</string>
			</array>
		</dict>
		<!--
		<key>NSSendFileTypes</key>
		<array>
			<string>public.file-url</string>
			<string>public.url</string>
			<string>public.item</string>
			<string>public.folder</string>
		</array>
		-->
		<key>NSSendTypes</key>
		<array>
			<string>NSStringPboardType</string>
			<string>NSURLPboardType</string>
			<string>public.utf8-plain-text</string>
			<string>public.url</string>
			<string>public.file-url</string>
		</array>
	</dict>
</array>

Commented out is the NSSendFileTypes key-value pair, which is similar in its functionality to NSSendTypes. According to the Services Implementation Guide the primary difference is that NSSendFileTypes only accepts Uniform Type Identifiers (UTIs), whereas NSSendTypes can accept the older style pasteboard types (e.g. NSStringPboardType, NSURLPboardType) and UTIs.

To localize strings for keys like NSMenuItem and NSServiceDescription, create a ServicesMenu.strings file with the translated strings. If a string (such as for NSServiceDescription) is particularly long, use a shorter token like SERVICE_DESCRIPTION for the key in the strings file.

The Code

From the AppDelegate's applicationDidFinishLaunching method, I call the following setupServiceProvider() method, which creates an instance of the ContextualMenuServiceProvider class and then calls NSUpdateDynamicServices() to dynamically refresh any new services.


func setupServiceProvider() {
	NSApplication.shared.servicesProvider = ContextualMenuServiceProvider()
	// Call this for sanity's sake to refresh the known services
	NSUpdateDynamicServices()
}
The ContextualMenuServiceProvider.swift file:

import Foundation
import Cocoa

class ContextualMenuServiceProvider: NSObject {

	@objc func eraseService(_ pasteboard: NSPasteboard, userData: String?, error: AutoreleasingUnsafeMutablePointer <NSString>) {
		
		// Just for reference, looking at the number of available pasteboard types
		if let pBoardTypes = pasteboard.types {
			NSLog("Number of pasteboard types: \(pBoardTypes.count)")
			NSLog("Pasteboard Types: \(pBoardTypes)")
		}
		
		// NSFilenamesPboardType is unavailable in Swift, use NSPasteboard.PasteboardType.fileURL
		guard let pboardInfo = pasteboard.string(forType: NSPasteboard.PasteboardType.fileURL) else { 
			NSLog("Could not find an appropriate pasteboard type")
			return
		}
	
		let urlPath = URL(fileURLWithPath: pboardInfo)
		let standardizedURL = URL(fileURLWithPath: pboardInfo).standardized
		let messageText = "Hola info \(pboardInfo) of type \(pboardInfoType) at \(urlPath.absoluteURL) with standardized URL \(standardizedURL)"
		NSLog(messageText)
	}
}

File System Path Types

For Permanent Eraser, I need to get the path for the selected file. When parsing out the returned file path from the pasteboard info, it returned an unintelligible path like: file:///.file/id=6571367.8622082855 . This is certainly not what I was expecting and resulted in some confusion until I learned more about the various methods that macOS can represent a file's path. What I was receiving here was a file reference URL. The advantage of this type is that it can point to the same file, even if the original file is moved (somewhat similar to how a Mac alias can still point to the correct file even if it is relocated). However, what I wanted was a path-based URL, which is easier for me to read and work with.

For most URLs, you build the URL by concatenating directory and file names together using the appropriate NSURL methods until you have the path to the item. A URL built in that way is referred to as a path-based URL because it stores the names needed to traverse the directory hierarchy to locate the item. (You also build string-based paths by concatenating directory and file-names together, with the results stored in a slightly different format than that used by the NSURL class.) In addition to path-based URLs, you can also create a file reference URL, which identifies the location of the file or directory using a unique ID.

All of the following entries are valid references to a file called MyFile.txt in a user’s Documents directory:

Path-based URL: file://localhost/Users/steve/Documents/MyFile.txt

File reference URL: file:///.file/id=6571367.2773272/

String-based path: /Users/steve/Documents/MyFile.txt

There are several ways to convert the file reference URL to a more readable format. Using a quick AppleScript from the Terminal, one can get the string-based path this way:

osascript -e 'get posix path of posix file "file:///.file/id=6571367.4833330"'

In Swift the conversion to a path-based URL is like:

let standardizedURL = URL(fileURLWithPath: pboardInfoFileURL).standardized

Testing

To test if your new service works, copy the application into the Applications folder and then launch your app to ensure that the system recognizes the new service. The NSUpdateDynamicServices() call is used to help refresh the system. However if it appears that macOS is not updating properly, then try running the following commands from the Terminal:

/System/Library/CoreServices/pbs -flush

To list the registered services, use pbs with the -dump_pboard option.

/System/Library/CoreServices/pbs -dump_pboard

References

« Newer posts Older posts »