How to create the main window (UWP/windows-rs) - windows

I am trying to use the UWP capture api Windows.Graphics.Capture to capture a window. For that I have to use the GraphicsCapturePicker to select a screen/window. But I get the error message:
Error { code: HRESULT(0x8000000E), message: "Could not create a new view because the main window has not yet been created" }
From this it is clear that in have to create the main window, but I don't know how to do that. The code example (C#) given by the windows docs (https://learn.microsoft.com/en-us/windows/uwp/audio-video-camera/screen-capture) just does it like this:
CoreWindow window = CoreApplication.MainView.CoreWindow;
await window.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, async () =>
{
await StartCaptureAsync();
});
However I cant find CoreApplication.MainView.CoreWindow anywhere in the windows-rs docs. The code that I currently have looks like this:
use windows;
fn window_dispatch() -> windows::core::Result<()> {
let picker = windows::Graphics::Capture::GraphicsCapturePicker::new().unwrap();
picker.PickSingleItemAsync().unwrap().get();
let return_value: windows::core::Result<()> = Ok(());
return return_value;
}
fn main() {
let support = windows::Graphics::Capture::GraphicsCaptureSession::IsSupported().unwrap();
println!("Support Capture: {}", support);
let handler = windows::UI::Core::DispatchedHandler::new(window_dispatch);
let prio = windows::UI::Core::CoreDispatcherPriority::Normal;
let window = windows::ApplicationModel::Core::CoreApplication::MainView().unwrap().CoreWindow().unwrap().Dispatch
Does anyone have information on how to create the main window or where this is documented in UWP?

Related

SwiftUI Update the mainMenu [SOLVED] kludgey

The Real Question
How do you update the mainMenu in SwiftUI so that it actually works?
I have built a MacOS Document Based application in SwiftUI which includes all of the in-built File menu commands (i.e. Close, Save, Duplicate. Rename... etc.)
Before saving the document, I validate the structure and would like to present a modal dialog to the user if there are any validation errors.
The modal dialog is just a simple OK/Cancel dialog - 'OK' meaning that the user is happy to save the file with validation errors, 'Cancel' would need to stop the save operation.
So the question is: "How do I intercept the in-built 'Save' menu command to present this dialog?
I have tried to overwrite the .saveItem CommandGroup - but this replaces all of the menu items and I only want to override a couple of the commands ('Save' and 'Save As') and don't want to re-implement them all (and I am not sure that I have the skills to do so)
.commands {
CommandGroup(replacing: .saveItem) {
// code goes here - but removes all of the in-built menus
}
}
I have tried this solution (In a SwiftUI Document App, how to save a document from within a function)
and have put it into my AppDelegate
public func applicationDidBecomeActive(_ notification: Notification) {
let menu = NSApplication.shared.mainMenu!.items.first(where: { $0.title == "File" })!
let submenu = menu.submenu!.items.first(where: { $0.title == "Save" })!
submenu.action = #selector(showDialog)
}
#objc func showDialog() {
var retVal: Int = 0
let thisWindow: NSWindow? = NSApplication.shared.mainWindow
let nsAlert: NSAlert = NSAlert()
let cancelButton: NSButton = nsAlert.addButton(withTitle: "Cancel")
cancelButton.tag = 1
let okButton: NSButton = nsAlert.addButton(withTitle: "OK")
okButton.tag = 0
// The below code is replaced
nsAlert.beginSheetModal(for: thisWindow!) { modalResponse in
print(modalResponse)
retVal = modalResponse.rawValue
if retVal == 0 {
print("save")
} else {
print("cancel")
}
}
}
However it doesn't actually call the showDialog function.
Edit/Update
I am still having difficulties updating the menus, but in the above example the call to beginModalSheet is incorrect as the process will run in the background. Updated the call to runModal() which will stop any background process writing the file.
#objc func showDialog() {
let nsAlert: NSAlert = NSAlert()
let cancelButton: NSButton = nsAlert.addButton(withTitle: "Cancel")
cancelButton.tag = 1
let okButton: NSButton = nsAlert.addButton(withTitle: "OK")
okButton.tag = 0
let response: Int = nsAlert.runModal().rawValue
if response == 0 {
print("save")
NSApp.sendAction(#selector(NSDocument.save(_:)), to: nil, from: nil)
} else {
print("cancel")
}
}
I have read somewhere that you need to set the menu before the window appears, and I have also read that you need to set the menus before the AppDelegate is set.
Yet another edit
See this post Hiding Edit Menu of a SwiftUI / MacOS app
and this comment
Thoughts: SwiftUI either has a bug or they really don't want you to remove the top level menus in NSApp.mainMenu. SwiftUI seems to reset the whole menu with no way to override or customize most details currently (Xcode 13.4.1). The CommandGroup(replacing: .textEditing) { }-esque commands don't let you remove or clear a whole menu. Assigning a new NSApp.mainMenu just gets clobbered when SwiftUI wants even if you specify no commands.
XCode 14.1
Swift 5
After a lot of super frustrating searching an attempts and lots of code - I reduced the problem to being just trying to change the name of the save menu item - If I could do this - then I can change the action for it as well.
Here is how I did it
My Tetsing App is called YikesRedux
Steps:
Register the AppDelegate
Override the applicationWillUpdate method
Put the menu updating in a DispatchQueue.main.async closure
Cry tears of joy that you have solved this problem after days of searching
YikesAppRedux.swift
import SwiftUI
#main
struct YikesReduxApp: App {
#NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate // <- Don't forget the AppDelegate
var body: some Scene {
DocumentGroup(newDocument: YikesReduxDocument()) { file in
ContentView(document: file.$document)
}
}
}
AppDelegate.swift
import Foundation
import AppKit
public class AppDelegate: NSObject, NSApplicationDelegate {
public func applicationWillUpdate(_ notification: Notification) {
DispatchQueue.main.async {
let currentMainMenu = NSApplication.shared.mainMenu
let fileMenu: NSMenuItem? = currentMainMenu?.item(withTitle: "File")
if nil != fileMenu {
let saveMenu = fileMenu?.submenu!.item(withTitle: "Save")
if nil != saveMenu {
print("updated menu")
saveMenu?.title = "Save Updated"
}
}
}
}
}
I put this down as a bit kludgey - as it runs on every application update (which is not a lot, but you can see the print out in the console "updated menu" when it does occur)
I did try to keep a state variable as to whether the menu was updated, to try and not do it again - but in a multi document window environment you would need to keep track of every window... (Also swift just clobbers the menu whenever it wants - so it didn't work as well as expected.)
I put the menu updating code in almost everywhere I could think of
Every single AppDelegate function override
init methods for the App, the ContentView
on the document read function/write function
You name it - I put it in there (I even had a hosting controller, a NSViewRepresentable)
I then removed them one by one until I found the solution.
I would be happy if there was a less kludgey way to do this.

is anyone able to restrict the type of the objects dropped on the mac in SwiftUI 3?

as per the documentation, it should be pretty straightforward. example for a List: https://developer.apple.com/documentation/swiftui/list/ondrop(of:istargeted:perform:)-75hvy#
the UTType should be the parameter restricting what a SwiftUI object can receive. in my case i want to accept only Apps. the UTType is .applicationBundle: https://developer.apple.com/documentation/uniformtypeidentifiers/uttype/3551459-applicationbundle
but it doesn't work. the SwiftUI object never changes status and never accepts the drop. the closure is never run. whether on Lists, H/VStacks, Buttons, whatever. the pdf type don't seem to work either, as well as many others. the only type that i'm able to use if fileURL, which is mainly like no restriction.
i'm not sure if i'm doing something wrong or if SwiftUI is half working for the mac.
here's the code:
List(appsToIgnore, id: \.self, selection: $selection) {
Text($0)
}
.onDrop(of: [.applicationBundle, .application], isTargeted: isTargeted) { providers in
print("hehe")
return true
}
replacing or just adding .fileURL in the UTType array makes the drop work but without any type restriction.
i've also tried to use .onInsert on a ForEach instead (https://developer.apple.com/documentation/swiftui/foreach/oninsert(of:perform:)-2whxl#), and to go through a proper DropDelegate (https://developer.apple.com/documentation/swiftui/dropdelegate#) but keep getting the same results. it would seem the SwiftUI drop for macOS is not yet working, but i can't find any official information about this. in the docs it is written macOS 11.0+ so i would expect it to work?
any info appreciated! thanks.
You need to validate manually, using DropDelegate of what kind of file is dragged over.
Here is a simplified demo of possible approach. Tested with Xcode 13 / macOS 11.6
let delegate = MyDelegate()
...
List(appsToIgnore, id: \.self, selection: $selection) {
Text($0)
}
.onDrop(of: [.fileURL], delegate: delegate) // << accept file URLs
and verification part like
class MyDelegate: DropDelegate {
func validateDrop(info: DropInfo) -> Bool {
// find provider with file URL
guard info.hasItemsConforming(to: [.fileURL]) else { return false }
guard let provider = info.itemProviders(for: [.fileURL]).first else { return false }
var result = false
if provider.canLoadObject(ofClass: String.self) {
let group = DispatchGroup()
group.enter() // << make decoding sync
// decode URL from item provider
_ = provider.loadObject(ofClass: String.self) { value, _ in
defer { group.leave() }
guard let fileURL = value, let url = URL(string: fileURL) else { return }
// verify type of content by URL
let flag = try? url.resourceValues(forKeys: [.contentTypeKey]).contentType == .applicationBundle
result = flag ?? false
}
// wait a bit for verification result
_ = group.wait(timeout: .now() + 0.5)
}
return result
}
func performDrop(info: DropInfo) -> Bool {
// handling code is here
return true
}
}

How to implement drag and drop to 3rd party apps in SwiftUI on macOS

I've tried to Google for the answer but all I find are how to accept drop from other apps and drag and drop in a single app, also mostly iOS focus and not many macOS related resources.
I writing a macOS toolbar app that I'm trying to implement drag and drop for the image the app create programatically and only in memory as NSImage. To allow the image to be draggable, I've implemented the a DraggableImage struct to be used in place of the Image view in the SwiftUI view:
struct DraggableImage: View {
let image: NSImage
var body: some View {
Image(nsImage: image)
.resizable()
.scaledToFit()
.padding()
.onDrag {
guard let tiffRepresentation = image.tiffRepresentation,
let bitmapImage = NSBitmapImageRep(data: tiffRepresentation),
let bitmapRepresentation = bitmapImage.representation(using: .png, properties: [:]) else {
return NSItemProvider(item: image.tiffRepresentation as NSSecureCoding?, typeIdentifier: kUTTypeTIFF as String)
}
let provider = NSItemProvider(item: bitmapRepresentation as NSSecureCoding?,
typeIdentifier: kUTTypePNG as String)
provider.previewImageHandler = { (handler, _, _) -> Void in
handler?(bitmapRepresentation as NSSecureCoding?, nil)
}
return provider
}
}
}
The DraggableImage struct works as expected if I am dragging the image onto an app like TextEdit or Email app. See below:
However, I could not get the picture to be draggable onto anything file based apps like the Finder window. See below:
Same thing dragging to the Desktop (which technically is a fullscreen Finder window):
What is missing in the implementation?
PS: The full demo project is hosted on GitHub for those interested in the implementation.
You need to export file URL instead of Data, so this file can be copied by MacOS.
Basic example:
let url = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("dragExport.png")
try! bitmapRepresentation.write(to: url)
let provider = NSItemProvider(item: url as NSSecureCoding?, typeIdentifier: kUTTypeFileURL as String)
provider.suggestedName = url.lastPathComponent

How do I read a text-file's content from it's shared Dropbox link in Swift 4.2 (without downloading)?

I'm having a hard time figuring out how to output a simple text-file's content from it's shared Dropbox link (without downloading) through Swift 4.2.
For Example:
let url = URL(string: "https://www.dropbox.com/s/rokwv82h54ogwy1/test.txt?dl=0")!
// the dropbox link above is a shared link so anyone can view it
do {
let content = try String(contentsOf: url)
print("File Content: \(content)")
} catch let error as NSError {
print("\(error)")
}
When I run this code I get this error:
Error Domain=NSCocoaErrorDomain Code=260 "The file “test.txt” couldn’t be opened because there is no such file."(there's more to the error but it's quite big)
Can anyone help me out please? Thanks.
There's more to the error but it's quite big
Do not strip error messages. If you don't know to fix this issue, you probably don't know what to strip to keep it valuable.
How to fix your problem
Select target
Switch to Signing & Capabilities tab
App Sandbox - Network - enable Outgoing Connections (Client)
Change the URL (dl=0) to (dl=1)
0 = display web page with a preview and download link
1 = do not display any web page, just serve the file
let url = URL(string: "https://www.dropbox.com/s/rokwv82h54ogwy1/test.txt?dl=1")!
// Change dl=0 to dl=1 ^
do {
let content = try String(contentsOf: url)
print("File Content: \(content)")
} catch let error as NSError {
print("\(error)")
}
Run again and you'll get:
File Content:
This is a test. If you can read this, you have passed! :)
Do not use String(contentsOf: url), because it's not async and it will block the main thread (UI).
Asynchronous example - imagine you have a view controller with one text field (label) and you'd like to display the file content there:
import Cocoa
class ViewController: NSViewController {
#IBOutlet var textField: NSTextField!
override func viewWillAppear() {
super.viewWillAppear()
textField.stringValue = "Loading ..."
loadRemoteFile()
}
func loadRemoteFile() {
let url = URL(string: "https://www.dropbox.com/s/rokwv82h54ogwy1/test.txt?dl=1")!
let task = URLSession.shared.dataTask(with: url) { data, _, error in
// Following code is not called on the main thread. If we'd like to
// modify UI elements, we have to dispatch our code on the main thread.
// Hence the DispatchQueue.main.async {}.
if let error = error {
print("Failed with error: \(error)")
DispatchQueue.main.async { self.textField.stringValue = "Failed" }
return
}
guard let data = data,
let content = String(data: data, encoding: .utf8) else {
print("Failed to decode data as an UTF-8 string")
DispatchQueue.main.async { self.textField.stringValue = "Failed" }
return
}
print("Content: \(content)")
DispatchQueue.main.async { self.textField.stringValue = content }
}
// At this point, we have a task which will download the file, but the task
// is not running. Every task is initially suspended.
task.resume() // Start the background task
// At this point, your program normally continues, because the download
// is executed in the background (not on the main thread).
}
}

Swift NSUserNotificationCenter didActivateNotification not working

I'm working on a swift os x application and I'm having trouble understanding the userNoticiation / didActivateNotification structure. I've reviewed the documentation and searched SO but am still stuck. Any guidance on the following code would be much appreciated:
func notify(message: String, callBack: String) {
println(message)
println(callBack)
var notification:NSUserNotification = NSUserNotification()
notification.title = "New Phone Call"
notification.informativeText = message
notification.actionButtonTitle = "Lookup"
notification.hasActionButton = true
var center:NSUserNotificationCenter = NSUserNotificationCenter.defaultUserNotificationCenter()
center.delegate = self
center.scheduleNotification(notification)
}
func notify (center: NSUserNotificationCenter, didActivateNotification notification: NSUserNotification){
center.delegate = self
println("clicked") //this does not print
}
The notification displays exactly as I'd like it to. Clicking the "Lookup" button that I've defined will bring my app to the foreground the first time it is clicked, but the code I'd expect to handle that action does not fire.
Thanks in advance.
Change your second 'func notify' declaration to 'optional func userNotificationCenter'

Resources