@ -0,0 +1,7 @@ |
|||
Copyright 2019 Bob Rudis |
|||
|
|||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: |
|||
|
|||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. |
|||
|
|||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
@ -0,0 +1,61 @@ |
|||
# RSwitch |
|||
|
|||
Change 'Current' R version on macOS |
|||
|
|||
## Description |
|||
|
|||
At the bottom of the [R for macOS Developer's Page](http://mac.r-project.org/) there's mention of an "other binary" called "RSwitch" that is _"a small GUI that allows you to switch between R versions quickly (if you have multiple versions of R framework installed)."_ Said switching requires you to use the "tar.gz" versions of R from the R for macOS Developer's Page since the official CRAN binary installers clean up after themselves quite nicely to prevent potentially wacky behavior. |
|||
|
|||
All this GUI does is change the `Current` alias target in `/Library/Frameworks/R.framework/Versions` to the appropriate version. You can do that from the command line but the switcher GUI was created so that means some folks prefer click-switching. |
|||
|
|||
After launching the app, there will be a new menu item in the main macOS menubar with a popup that will look something like this: |
|||
|
|||
![](rswitch-menu-example.png) |
|||
|
|||
The checkmarked version is the default (i.e. the one `Current` points to) and if you select any other version the alias will be changed. You can also launch a new Finder window that opens to the R frameworks directory or quit the app. |
|||
|
|||
## Why? |
|||
|
|||
- I needed to brush up on Swift 5 coding |
|||
- I wanted RSwitch as a menubar app vs one with a dialog that I could easily lose across 15 desktops |
|||
- I wanted to see if it was possible to make it work sandboxed (TLDR: it isn't) |
|||
- The existing RSwitch source code is not available (likely got lost in the move from AT&T's domain to the new `mac.r-project.org` domain) |
|||
- The existing RSwitch binaries no longer work starting with macOS Catalina since they are 32-bit |
|||
- I'm not exactly enamored with the old-school R icons, including the RSwitch one. That's a personal preference, not a judgement or dig on the existing icons or the creators of said icons. |
|||
|
|||
## What's Inside the Tin? |
|||
|
|||
The `releases` directory contains ZIP files of various versions (see TODO for why there'll be future versions). Just download and extract and enjoy. |
|||
|
|||
Other than that (and the example `png` & `LICENSE` file) the rest is a _super small_ Xcode project. It's commented as well to let you know what's going on. |
|||
|
|||
## CAUTION |
|||
|
|||
You should really build this yourself since installing random binaries from the internet (or, even official ones from things like app stores) is fraught with peril. You have no guarantee that the releases are, indeed, built from this source tree. I could be stealing all your stuff! (NOTE: I'm not). Reading and understanding the source code and building the app on your own is the only way to ensure you're not going to be compromised. |
|||
|
|||
## Contributing |
|||
|
|||
- Follow the [Contributor Code of Conduct](https://www.contributor-covenant.org/version/1/0/0/code-of-conduct.html) |
|||
- See the TODO (below) |
|||
- File an issue claiming an item (on any social coding site you like) |
|||
- Ensure you have [signed commits](https://git-scm.com/book/ms/v2/Git-Tools-Signing-Your-Work) |
|||
- File a PR or submit a patch via email (bob at rud dot is) |
|||
|
|||
## TODO |
|||
|
|||
- Allow hiding of the app icon (not sure this is a good idea, tho…pls discuss in an issue!) |
|||
- Add Cmd-1, -2, -3, (etc) key equivalents in the menu bar for fast selection (one reason why ^^ might not be a good idea) |
|||
- Clean up the icon (which is "dial" by IconMark from the Noun Project). This means having it look better in the menu bar in dark/light mode _including_ the highlight mode for it. Possibly means getting a visible "R" on it somewhere. |
|||
- Better/prettier alerting (which also means more sanity checks) |
|||
- Add an "about" box (mostly to ensure IconMark gets more credit than a comment and README) |
|||
- (add your own TODO suggestions via PR) |
|||
|
|||
## License |
|||
|
|||
Copyright (c) 2019 Bob Rudis, MIT License |
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
@ -1,26 +1,149 @@ |
|||
// |
|||
// AppDelegate.swift |
|||
// RSwitch |
|||
// |
|||
// Created by hrbrmstr on 8/22/19. |
|||
// Copyright © 2019 Bob Rudis. All rights reserved. |
|||
// |
|||
|
|||
import Cocoa |
|||
|
|||
@NSApplicationMain |
|||
class AppDelegate: NSObject, NSApplicationDelegate { |
|||
|
|||
// Show an informational alert |
|||
public func infoAlert(_ message: String, _ extra: String? = nil, style: NSAlert.Style = NSAlert.Style.informational) { |
|||
|
|||
let alert = NSAlert() |
|||
|
|||
alert.messageText = message |
|||
|
|||
if extra != nil { alert.informativeText = extra! } |
|||
|
|||
alert.alertStyle = style |
|||
|
|||
alert.runModal() |
|||
} |
|||
|
|||
// Show an informational alert and then quit |
|||
public func quitAlert(_ message: String, _ extra: String? = nil) { |
|||
|
|||
infoAlert(message, "The application will now quit.", style: NSAlert.Style.critical) |
|||
|
|||
NSApp.terminate(nil) |
|||
|
|||
} |
|||
|
|||
@NSApplicationMain |
|||
class AppDelegate: NSObject, NSApplicationDelegate { |
|||
|
|||
// Where the official R installs go |
|||
let macos_r_framework_dir = "/Library/Frameworks/R.framework/Versions" |
|||
|
|||
// Get the bar setup |
|||
let statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength) |
|||
let statusMenu = NSMenu() |
|||
|
|||
let quitItem = NSMenuItem(title: NSLocalizedString("Quit", comment: "Quit menu item"), action: #selector(NSApp.terminate), keyEquivalent: "q") |
|||
|
|||
override init() { |
|||
super.init() |
|||
|
|||
statusMenu.delegate = self |
|||
|
|||
// dial by IconMark from the Noun Project |
|||
statusItem.button?.image = #imageLiteral(resourceName: "RSwitch") |
|||
statusItem.menu = statusMenu |
|||
} |
|||
|
|||
func applicationDidFinishLaunching(_ aNotification: Notification) { |
|||
// Insert code here to initialize your application |
|||
// Insert code here to initialize app |
|||
} |
|||
|
|||
|
|||
func applicationWillTerminate(_ aNotification: Notification) { |
|||
// Insert code here to tear down your application |
|||
// Insert code here to tear down app |
|||
} |
|||
|
|||
|
|||
// The core worker function. Receives the basename of the selected directory |
|||
// then removes the current alias and creates the new one. |
|||
@objc func handleSwitch(_ sender: NSMenuItem?) { |
|||
|
|||
let fm = FileManager.default; |
|||
let title = sender?.title |
|||
|
|||
do { |
|||
try fm.removeItem(atPath: macos_r_framework_dir + "/" + "Current") |
|||
} catch { |
|||
infoAlert("Failed to remove 'Current' alias", macos_r_framework_dir + "/" + "Current") |
|||
} |
|||
|
|||
do { |
|||
try fm.createSymbolicLink( |
|||
at: NSURL(fileURLWithPath: macos_r_framework_dir + "/" + "Current") as URL, |
|||
withDestinationURL: NSURL(fileURLWithPath: macos_r_framework_dir + "/" + title!) as URL |
|||
) |
|||
} catch { |
|||
infoAlert("Failed to create alias for " + macos_r_framework_dir + "/" + title!) |
|||
} |
|||
|
|||
} |
|||
|
|||
// Show the framework dir in a new Finder window |
|||
@objc func openFrameworksDir(_ sender: NSMenuItem?) { |
|||
NSWorkspace.shared.openFile(macos_r_framework_dir, withApplication: "Finder") |
|||
} |
|||
|
|||
} |
|||
|
|||
extension AppDelegate: NSMenuDelegate { |
|||
|
|||
func menuWillOpen(_ menu: NSMenu) { |
|||
|
|||
if (menu != self.statusMenu) { return } |
|||
|
|||
// clear the menu |
|||
menu.removeAllItems() |
|||
|
|||
// add selection to open frameworks dir in Finder |
|||
menu.addItem(NSMenuItem(title: "Open R Frameworks Directory", action: #selector(openFrameworksDir), keyEquivalent: "" )) |
|||
menu.addItem(NSMenuItem.separator()) |
|||
|
|||
// populate installed versions |
|||
let fm = FileManager.default |
|||
var targetPath:String? = nil |
|||
|
|||
do { |
|||
|
|||
// gets a directory listing |
|||
let entries = try fm.contentsOfDirectory(atPath: macos_r_framework_dir) |
|||
|
|||
// retrieves all versions (excludes hidden files and the Current alias |
|||
let versions = entries.sorted().filter { !($0.hasPrefix(".")) && !($0 == "Current") } |
|||
|
|||
// if there was a Current alias (prbly shld alert if not) |
|||
if ((entries.filter { $0 == "Current" })[0] == "Current") { |
|||
|
|||
// get where Current points to |
|||
let furl = NSURL(fileURLWithPath: macos_r_framework_dir + "/" + "Current") |
|||
|
|||
if (furl.fileReferenceURL() != nil) { |
|||
do { |
|||
let fdat = try NSURL(resolvingAliasFileAt: furl as URL, options: []) |
|||
targetPath = fdat.lastPathComponent! |
|||
} catch { |
|||
targetPath = furl.path |
|||
} |
|||
} |
|||
|
|||
// populate menu items with all installed R versions, ensuring we |
|||
// put a checkbox next to the one that is Current |
|||
for version in versions { |
|||
let item = NSMenuItem(title: version, action: #selector(handleSwitch), keyEquivalent: "") |
|||
item.isEnabled = true |
|||
if (version == targetPath) { item.state = NSControl.StateValue.on } |
|||
item.representedObject = version |
|||
menu.addItem(item) |
|||
} |
|||
|
|||
} |
|||
|
|||
} catch { |
|||
quitAlert("Failed to list contents of R framework directory. You either do not have R installed or have incorrect permissions set on " + macos_r_framework_dir) |
|||
} |
|||
|
|||
// Add a Quit item |
|||
menu.addItem(NSMenuItem.separator()) |
|||
menu.addItem(quitItem) |
|||
|
|||
} |
|||
|
|||
} |
|||
|
Depois Largura: | Altura: | Tamanho: 4.3 KiB |
Depois Largura: | Altura: | Tamanho: 9.9 KiB |
Depois Largura: | Altura: | Tamanho: 381 B |
Depois Largura: | Altura: | Tamanho: 821 B |
Depois Largura: | Altura: | Tamanho: 9.9 KiB |
Depois Largura: | Altura: | Tamanho: 21 KiB |
Depois Largura: | Altura: | Tamanho: 821 B |
Depois Largura: | Altura: | Tamanho: 1.8 KiB |
Depois Largura: | Altura: | Tamanho: 21 KiB |
Depois Largura: | Altura: | Tamanho: 46 KiB |
@ -0,0 +1,18 @@ |
|||
{ |
|||
"images" : [ |
|||
{ |
|||
"idiom" : "mac", |
|||
"filename" : "rs-2x.pdf", |
|||
"scale" : "1x" |
|||
}, |
|||
{ |
|||
"idiom" : "mac", |
|||
"filename" : "rs-2x.pdf", |
|||
"scale" : "2x" |
|||
} |
|||
], |
|||
"info" : { |
|||
"version" : 1, |
|||
"author" : "xcode" |
|||
} |
|||
} |
@ -1,10 +0,0 @@ |
|||
<?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>com.apple.security.app-sandbox</key> |
|||
<true/> |
|||
<key>com.apple.security.files.user-selected.read-only</key> |
|||
<true/> |
|||
</dict> |
|||
</plist> |
Depois Largura: | Altura: | Tamanho: 18 KiB |