Opening a file from SafariWebExtensionHandler - swift

I'm developing a Safari Web Extension. Among other things it has to open files on the user's disc (in their corresponding apps, like "Preview" app for png file and so on) when user clicks a browser contextual menu item.
But if I try to do something like this (for testing purposes):
class SafariWebExtensionHandler: NSObject, NSExtensionRequestHandling {
func beginRequest(with context: NSExtensionContext) {
NSWorkspace.shared.open(URL(fileURLWithPath: "/Users/username/Downloads/banner.png"))
}
}
I get the following error:
The application “(null)” does not have permission to open “banner.png.”
Which is weird, because this works fine:
NSWorkspace.shared.selectFile("/Users/username/Downloads/banner.png", inFileViewerRootedAtPath: "/Users/username/Downloads")
Will be grateful for any suggestions
I tried calling shell script to open the file, but it doesn't work either.

Related

macOS native coding with browsers, how to open a url in existing tab from obj-c/swift?

I've developed a command palette for macOS. Over the last few days I've figured out a way to show the Notification Center inside my app.
I would like to mimic the behaviour of the real Notification Center when clicking a notification: open the corresponding tab when clicking in a browser notification.
However whenever I cannot figure out a way to do it programmatically, I can only tell macOS to open the link and it automatically opens the default browser and creates a new tab.
let url = URL(string: "https://www.google.com")! // this opens a new tab everytime
if NSWorkspace.shared.open(url) {
print("default browser was successfully opened")
}
As for the notification itself I do get all the info it contains: the app bundle id, the notification payload, and even a link (e.g. n#https://web.whatsapp.com#465718293123#c.us). I've seen some answers that rely on AppleScript but I would rather avoid it if possible. But if not possible... then happy to fallback to it.
Any ideas how to achieve this? Many thanks!
P.D. I've also tried some variations of the link such as: arc://web.whatsapp.com#1234567 or arc://n#web.whatsapp.com#1234567. At most this focuses on the browser but not on the tab.

Unity does not open the Edge Browser on a Hololens 2 when providing a pdf file path

I'm trying to open a PDF within a Unity app deployed to a Microsoft Hololens 2 using the MRTK.
I want to use the integrated Edge Browser, that is able to load within the app and to run simulatenously.
Edge by itself is also able to load pdf files.
I expected using LaunchUri would open the file from a provided location
public void Launch()
{
var absolutePath = Path.Combine(_myDocuments, _subfolder);
absolutePath = Path.Combine(absolutePath, _fileName);
#if UNITY_WSA
UnityEngine.WSA.Launcher.LaunchUri(absolutePath, true);
#else
Application.OpenURL(absolutePath);
#endif
}
Launching the Edge Browser with a website as the string, works. Entering the file path manually into the browser window to load the pdf also works, but launching the Browser by directly providing the full path to my file, does not open any browser window.
I suspect, that it doesn't work because Edge is not setup to be the default app for PDF files, but when using the button within Edge to make it the default app, it just tells me that it failed and I can't find any other way to achieve that.
Does anyone else have an idea how I would be able to load Edge from within an Unity app and providing the path to my pdf file so it automatically loads on a button press?
The PDF is stored on the local file system under "Documents".
As mentioned, the file is accessible and can be opened by Edge. Using the system File Browser to manually open the PDF from within the system automatically uses Edge and opens the file as expected, which is the behaviour I would like to copy from within Unity.
Environment
Unity 2020.3.15f
Hololens 2 latest Update
Win10 SDK target 10.0.19041.0
Build to ARM64 for Hololens
edit:
I ended up using
UnityEngine.WSA.Launcher.LaunchFile(folderType, relativePath, false);
You can try Launcher.LaunchFileAsync to launch a PDF file with Edge. This method required a StorageFile as the parameter, which can be got by FileOpenPicker or GetFileFromPathAsync.
Please refer to the following code.
private async void PickAndLaunchFile()
{
// First, get a file via the picker.
var openPicker = new Windows.Storage.Pickers.FileOpenPicker();
openPicker.FileTypeFilter.Add("*");
Windows.Storage.StorageFile file = await openPicker.PickSingleFileAsync();
if (file != null)
{
// Next, launch the file.
bool success = await Launcher.LaunchFileAsync(file);
}

Accessibility Permissions for Development in macOS and XCode

Is there a way to give an app I am developing in XCode accessibility permissions by default during development. The idea is that I could hit the run key and test the new code without having to jump through the hoops in the settings. For deployment obviously it wouldn't work, but for development is there a way to basically whitelist the app?
EDIT: This is a new method I've found/created that is working and has the added benefit of prompting the user first too, improving the onboarding experience of your app. The original method (which I've left at the bottom of this post) still prompted for accessibility on each new build of the app unfortunately.
I had to get this working in my macOS app and had another idea and way to approach it, given the user needs to action it anyway, I just have the app present the required dialogue on each run and during the app build, I run a script to clear the existing permissions.
For reference, see v1.3.0 of my Auto Clicker macOS app, specifically:
Services/PermissionsService.swift (whole class)
Init/AppDelegate.swift (line 21)
Init/AutoClickerApp.swift (line 32 & 40)
auto-clicker.xcodeproj/project.pbxproj (line 435)
The links are links to the tagged version, so should never change or break. There is quite a bit of code, I'll try to summarise here.
Firstly, I created a new class to manage permissions related functionality to keep things contextual:
//
// PermissionsService.swift
// auto-clicker
//
// Created by Ben on 10/04/2022.
//
import Cocoa
final class PermissionsService: ObservableObject {
// Store the active trust state of the app.
#Published var isTrusted: Bool = AXIsProcessTrusted()
// Poll the accessibility state every 1 second to check
// and update the trust status.
func pollAccessibilityPrivileges() {
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
self.isTrusted = AXIsProcessTrusted()
if !self.isTrusted {
self.pollAccessibilityPrivileges()
}
}
}
// Request accessibility permissions, this should prompt
// macOS to open and present the required dialogue open
// to the correct page for the user to just hit the add
// button.
static func acquireAccessibilityPrivileges() {
let options: NSDictionary = [kAXTrustedCheckOptionPrompt.takeRetainedValue() as NSString: true]
let enabled = AXIsProcessTrustedWithOptions(options)
}
}
Then, I added the following to my AppDelegate (baring in mind this was in Swift UI):
//
// AppDelegate.swift
// auto-clicker
//
// Created by Ben on 30/03/2022.
//
import Foundation
import Cocoa
final class AppDelegate: NSObject, NSApplicationDelegate {
// When the application finishes launching, request the
// accessibility permissions from the service class we
// made earlier.
func applicationDidFinishLaunching(_ notification: Notification) {
PermissionsService.acquireAccessibilityPrivileges()
}
}
Then finally, in our Swift UI app init, lets add some UI feedback to the user that we are waiting for permissions and listen to that isTrusted published property we setup earlier in the permissions service class that gets polled every second to unlock the UI when the user has granted the required permissions:
//
// AutoClickerApp.swift
// auto-clicker
//
// Created by Ben on 12/05/2021.
//
import Foundation
import SwiftUI
import Defaults
#main
struct AutoClickerApp: App {
// Create an instance of the permissions service class that we
// can observe for the trusted state change.
#StateObject private var permissionsService = PermissionsService()
var body: some Scene {
// Wait for trust permissions being granted from the user,
// displaying a blocking permissions view telling the user
// what to do and then presenting the main application view
// automatically when the required trust permissions are granted.
WindowGroup {
if self.permissionsService.isTrusted {
MainView()
} else {
PermissionsView()
}
.onAppear(perform: self.permissionsService.pollAccessibilityPrivileges)
}
}
}
You can see the blocking view I made in the app above, Views/Main/PermissionsView.swift.
Then in order to automatically clear the permissions during the app build, I added a new build script to the project that runs the following against /bin/sh:
tccutil reset Accessibility $PRODUCT_BUNDLE_IDENTIFIER
As seen in auto-clicker.xcodeproj/project.pbxproj (line 435).
This means I'll be prompted with the system dialogue on each app build to just press the + button and add the app.
Unfortunately this is the most frictionless way I've found to develop the app with these permissions being required.
Outdated answer:
Found a way to do this after some trial and error, navigating through Xcode's (>11, currently 13) new build system.
With Xcode open and having it as the foreground app (so it takes over the menu bar with its menu items), do the following:
From the menu bar, select 'Xcode'
If your project doesn't yet have a workspace, click 'Save as Workspace...' near the bottom of the list and save the Workspace alongside your *.xcodeproj so they should be in the same directory. From now on you'll open your project via the new *.xcworkspace Workspace instead of your *.xcodeproj Project.
From the menu bar, select 'Xcode' again
Click 'Workspace Settings...' near the bottom of the list
Under 'Derived Data' select 'Workspace-relative Location', if you want to customise the path do so now
Click 'Done' in the bottom right
This puts the build location of our *.app binary within the project so its easy to find, along with also allowing us to check the change into source control as we now have the Workspace settings stored in the *.xcworkspace file.
Next we need to now point the permissions at the above build binaries location, so:
Open System Preferences
Click 'Security & Privacy'
Click the padlock in the bottom right to make changes
Select 'Accessibility' from the left side list
Click the plus button at the bottom left of the list and find the *.app file to add it to the list that we put within the project directory, this should be something like $PROJECT_DIR/DerivedData/$PROJECT/Build/Products/Debug/*.app
Click the checkbox to the left of the app to check it if its not already checked
Restart the app
Any builds should now have the relevant permissions.
Just to note though, this will overwrite the permissions of any archived/prod builds as the app names and binaries are the same. They are easily added back though, just delete the permission for the build app and instead point it back at your prod app, usually in /Applications.

print not working in Swift 3 extensions

I'm new at Swift 3 and I try to make a print("Test") in a Widget extension.
I tried the same code in ViewController.swift and It works ok. I don't know why it works there but it doesn't on TodayViewController.swift. I can't access to objects too.
func loadData() {
let query = PFQuery(className: "Noticias")
query.whereKey("titulo", equalTo:"Es Navidad")
query.findObjectsInBackground(block: { (objects : [PFObject]?, error: Error?) -> Void in
if error == nil {
// The find succeeded.
print("Successfully retrieved \(objects!.count) scores.")
// Do something with the found objects
if let objects = objects {
for object in objects {
print(object.objectId!)
}
}
} else {
// Log details of the failure
print("bad day homie")
print(error!)
}
})
}
I attach I picture to see it clearly. If I try to print on the file marked as Work, it works. But if I try it on the file marked ad NO, it doesn't.
It is extremely difficult to retrieve print messages from an extension. The problem is that it's an extension! It isn't running in your app, so it doesn't arrive at your console. Sometimes I find you can solve this problem by switching the debugged process in the Debug Bar at the top of the debug area (at the bottom of the screen, not shown in your screen shot), but at other times this doesn't work.
I'll illustrate a possible technique that seems to be pretty reliable. Look at this screen shot:
"Expand" is an action extension. But my containing app is called "bk2ch13...". So how will I ever manage to pause at the breakpoint shown at the right, which is in the action extension? This is what I do.
First, with the screen as shown above, I build and run my containing app.
Then, I switch the target to the action extension:
Now I build and run again. But now I am trying to run an action extension, which you can't do, so Xcode asks me what app to run:
So I choose "bk2ch13...". So now we are running my host app again, but we are debugging the extension. So I use my host app to exercise the extension, and sure enough, we pause at the breakpoints and print statements arrive into the console.
Note, in that screen shot, how the debug bar clearly shows that we are talking to the extension, not the host app.

IOS/IPHONE Safari or Chrome viewing local files

I have created a stand-alone HTML, CSS, JavaScript offline map view using Leaflet (see leafletjs.com). I can use FileApp/DiskAid to download the application to the iphone. I can tap on the HTML file and view the app, but as you can see from the attached screen shot, the layers and zoom controls are at the top of the screen. If I try and press them then your HTML viewer with block the press and slide down a banner (BACK, ACTIONS, etc) (the black top banner that your viewer shows).
Better yet, I would like to open it with SAFARI since it has the NITRO JS engine, but the only OPEN IN options I get are Evernote, RiddleDocs, Mover, and DropBox.
Is there way to tell safari or chrome for IOS to open up a local file system where the html is: http:// 127.0.0.1/fileApp/Toby.html for example where Toby is my HTML file? I realize is somewhere it the data for t
First Step :-
These options are configured using keys in your Info.plist file. The first key is UIFileSharingEnabled, which enables iTunes sharing of files in your Documents folder. The second key is LSSupportsOpeningDocumentsInPlace, which grants the local file provider access to files in your Documents folder. Add these keys to your Info.plist and set their values to YES.
enter image description here
enter image description here
let documentPicker = UIDocumentPickerViewController(documentTypes: [String(kUTTypePDF)], in: .import)
documentPicker.delegate = self
documentPicker.modalPresentationStyle = .overCurrentContext
present(documentPicker, animated: true, completion: nil)
You can use option either to import or export files from app. Also you can define the type of files you want to export or import...