From c94778961ea102f4b35d473c3f352fddba0471e8 Mon Sep 17 00:00:00 2001 From: Kei Kamikawa Date: Tue, 30 Aug 2022 01:02:24 +0900 Subject: [PATCH 1/5] fixed to back to Go when finished window application --- virtualization_view.h | 10 ++++++++++ virtualization_view.m | 34 +++++++++++++++++++++++++++++----- 2 files changed, 39 insertions(+), 5 deletions(-) diff --git a/virtualization_view.h b/virtualization_view.h index d5732b7..8e38912 100644 --- a/virtualization_view.h +++ b/virtualization_view.h @@ -10,6 +10,16 @@ #import #import +@interface VZApplication : NSApplication +{ + bool shouldKeepRunning; +} + +- (void)run; +- (void)terminate:(id)sender; + +@end + @interface AboutViewController : NSViewController - (instancetype)init; @end diff --git a/virtualization_view.m b/virtualization_view.m index 4168e57..423f704 100644 --- a/virtualization_view.m +++ b/virtualization_view.m @@ -6,6 +6,32 @@ #import "virtualization_view.h" +@implementation VZApplication + +- (void)run +{ + [self finishLaunching]; + + shouldKeepRunning = YES; + do + { + NSEvent *event = [self + nextEventMatchingMask:NSEventMaskAny + untilDate:[NSDate distantFuture] + inMode:NSDefaultRunLoopMode + dequeue:YES]; + // NSLog(@"event: %@", event); + [self sendEvent:event]; + [self updateWindows]; + } while (shouldKeepRunning); +} + +- (void)terminate:(id)sender +{ + shouldKeepRunning = NO; +} +@end + @implementation AboutViewController {} - (instancetype)init @@ -151,7 +177,6 @@ - (instancetype)initWithVirtualMachine:(VZVirtualMachine *)virtualMachine /* IMPORTANT: delegate methods are called from VM's queue */ - (void)guestDidStopVirtualMachine:(VZVirtualMachine *)virtualMachine { - NSLog(@"VM %@ guest stopped", virtualMachine); [NSApp performSelectorOnMainThread:@selector(terminate:) withObject:self waitUntilDone:NO]; } @@ -171,9 +196,8 @@ - (void)applicationDidFinishLaunching:(NSNotification *)notification { [NSApp activateIgnoringOtherApps:YES]; } -- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender -{ - return YES; +- (void)windowWillClose:(NSNotification *)notification { + [NSApp performSelectorOnMainThread:@selector(terminate:) withObject:self waitUntilDone:NO]; } - (void)setupGraphicWindow @@ -185,10 +209,10 @@ - (void)setupGraphicWindow [window setOpaque:NO]; [window setContentView:_virtualMachineView]; - // [window setDelegate:self]; [window setTitleVisibility:NSWindowTitleHidden]; [window center]; + [window setDelegate:self]; [window makeKeyAndOrderFront:nil]; // This code to prevent crash when called applicationShouldTerminateAfterLastWindowClosed. From 481c580c8d058b938091d5fe58a7a5ede38c174b Mon Sep 17 00:00:00 2001 From: Kei Kamikawa Date: Tue, 30 Aug 2022 01:07:57 +0900 Subject: [PATCH 2/5] fixed initialization and use cgo Handler --- virtualization.go | 74 ++++++++++++++--------------------------------- virtualization.h | 11 ++++--- virtualization.m | 50 +++++++++++++------------------- 3 files changed, 46 insertions(+), 89 deletions(-) diff --git a/virtualization.go b/virtualization.go index 18c5666..6a7c05b 100644 --- a/virtualization.go +++ b/virtualization.go @@ -8,12 +8,17 @@ package vz import "C" import ( "runtime" + "runtime/cgo" "sync" "unsafe" "github.com/rs/xid" ) +func init() { + C.sharedApplication() +} + // VirtualMachineState represents execution state of the virtual machine. type VirtualMachineState int @@ -77,17 +82,9 @@ type ( mu sync.RWMutex } - machineHandlers struct { - start func(error) - pause func(error) - resume func(error) - } ) -var ( - handlers = map[string]*machineHandlers{} - statuses = map[string]*machineStatus{} -) +var statuses = map[string]*machineStatus{} // NewVirtualMachine creates a new VirtualMachine with VirtualMachineConfiguration. // @@ -104,11 +101,6 @@ func NewVirtualMachine(config *VirtualMachineConfiguration) *VirtualMachine { state: VirtualMachineState(0), stateNotify: make(chan VirtualMachineState), } - handlers[id] = &machineHandlers{ - start: func(error) {}, - pause: func(error) {}, - resume: func(error) {}, - } dispatchQueue := C.makeDispatchQueue(cs.CString()) v := &VirtualMachine{ id: id, @@ -203,37 +195,16 @@ func (v *VirtualMachine) CanRequestStop() bool { return (bool)(C.vmCanRequestStop(v.Ptr(), v.dispatchQueue)) } -//export startHandler -func startHandler(errPtr unsafe.Pointer, cid *C.char) { - id := (*char)(cid).String() - // If returns nil in the cgo world, the nil will not be treated as nil in the Go world - // so this is temporarily handled (Go 1.17) - if err := newNSError(errPtr); err != nil { - handlers[id].start(err) - } else { - handlers[id].start(nil) - } -} +//export virtualMachineCompletionHandler +func virtualMachineCompletionHandler(cgoHandlerPtr, errPtr unsafe.Pointer) { + cgoHandler := *(*cgo.Handle)(cgoHandlerPtr) -//export pauseHandler -func pauseHandler(errPtr unsafe.Pointer, cid *C.char) { - id := (*char)(cid).String() - // see: startHandler - if err := newNSError(errPtr); err != nil { - handlers[id].pause(err) - } else { - handlers[id].pause(nil) - } -} + handler := cgoHandler.Value().(func(error)) -//export resumeHandler -func resumeHandler(errPtr unsafe.Pointer, cid *C.char) { - id := (*char)(cid).String() - // see: startHandler if err := newNSError(errPtr); err != nil { - handlers[id].resume(err) + handler(err) } else { - handlers[id].resume(nil) + handler(nil) } } @@ -251,10 +222,9 @@ func makeHandler(fn func(error)) (func(error), chan struct{}) { // The error parameter passed to the block is null if the start was successful. func (v *VirtualMachine) Start(fn func(error)) { h, done := makeHandler(fn) - handlers[v.id].start = h - cid := charWithGoString(v.id) - defer cid.Free() - C.startWithCompletionHandler(v.Ptr(), v.dispatchQueue, cid.CString()) + handler := cgo.NewHandle(h) + defer handler.Delete() + C.startWithCompletionHandler(v.Ptr(), v.dispatchQueue, unsafe.Pointer(&handler)) <-done } @@ -264,10 +234,9 @@ func (v *VirtualMachine) Start(fn func(error)) { // The error parameter passed to the block is null if the start was successful. func (v *VirtualMachine) Pause(fn func(error)) { h, done := makeHandler(fn) - handlers[v.id].pause = h - cid := charWithGoString(v.id) - defer cid.Free() - C.pauseWithCompletionHandler(v.Ptr(), v.dispatchQueue, cid.CString()) + handler := cgo.NewHandle(h) + defer handler.Delete() + C.pauseWithCompletionHandler(v.Ptr(), v.dispatchQueue, unsafe.Pointer(&handler)) <-done } @@ -277,10 +246,9 @@ func (v *VirtualMachine) Pause(fn func(error)) { // The error parameter passed to the block is null if the resumption was successful. func (v *VirtualMachine) Resume(fn func(error)) { h, done := makeHandler(fn) - handlers[v.id].resume = h - cid := charWithGoString(v.id) - defer cid.Free() - C.resumeWithCompletionHandler(v.Ptr(), v.dispatchQueue, cid.CString()) + handler := cgo.NewHandle(h) + defer handler.Delete() + C.resumeWithCompletionHandler(v.Ptr(), v.dispatchQueue, unsafe.Pointer(&handler)) <-done } diff --git a/virtualization.h b/virtualization.h index 196e409..0e71e75 100644 --- a/virtualization.h +++ b/virtualization.h @@ -10,9 +10,7 @@ #import /* exported from cgo */ -void startHandler(void *err, char *id); -void pauseHandler(void *err, char *id); -void resumeHandler(void *err, char *id); +void virtualMachineCompletionHandler(void *cgoHandler, void *errPtr); void connectionHandler(void *connection, void *err, char *id); void changeStateOnObserver(int state, char *id); bool shouldAcceptNewConnectionHandler(void *listener, void *connection, void *socketDevice); @@ -104,9 +102,9 @@ void *newVZGenericPlatformConfiguration(); /* VirtualMachine */ void *newVZVirtualMachineWithDispatchQueue(void *config, void *queue, const char *vmid); bool requestStopVirtualMachine(void *machine, void *queue, void **error); -void startWithCompletionHandler(void *machine, void *queue, const char *vmid); -void pauseWithCompletionHandler(void *machine, void *queue, const char *vmid); -void resumeWithCompletionHandler(void *machine, void *queue, const char *vmid); +void startWithCompletionHandler(void *machine, void *queue, void *completionHandler); +void pauseWithCompletionHandler(void *machine, void *queue, void *completionHandler); +void resumeWithCompletionHandler(void *machine, void *queue, void *completionHandler); bool vmCanStart(void *machine, void *queue); bool vmCanPause(void *machine, void *queue); bool vmCanResume(void *machine, void *queue); @@ -124,4 +122,5 @@ typedef struct VZVirtioSocketConnectionFlat VZVirtioSocketConnectionFlat convertVZVirtioSocketConnection2Flat(void *connection); +void sharedApplication(); void startVirtualMachineWindow(void *machine, double width, double height); \ No newline at end of file diff --git a/virtualization.m b/virtualization.m index fa04677..b1a9a8c 100644 --- a/virtualization.m +++ b/virtualization.m @@ -824,45 +824,31 @@ bool requestStopVirtualMachine(void *machine, void *queue, void **error) return queue; } -typedef void (^handler_t)(NSError *); - -handler_t generateHandler(const char *vmid, void handler(void *, char *)) +void startWithCompletionHandler(void *machine, void *queue, void *completionHandler) { - handler_t ret; - @autoreleasepool { - NSString *str = [NSString stringWithUTF8String:vmid]; - ret = Block_copy(^(NSError *err){ - handler(err, copyCString(str)); - }); - } - return ret; -} - -void startWithCompletionHandler(void *machine, void *queue, const char *vmid) -{ - handler_t handler = generateHandler(vmid, startHandler); dispatch_sync((dispatch_queue_t)queue, ^{ - [(VZVirtualMachine *)machine startWithCompletionHandler:handler]; + [(VZVirtualMachine *)machine startWithCompletionHandler:^(NSError *err) { + virtualMachineCompletionHandler(completionHandler, err); + }]; }); - Block_release(handler); } -void pauseWithCompletionHandler(void *machine, void *queue, const char *vmid) +void pauseWithCompletionHandler(void *machine, void *queue, void *completionHandler) { - handler_t handler = generateHandler(vmid, pauseHandler); dispatch_sync((dispatch_queue_t)queue, ^{ - [(VZVirtualMachine *)machine pauseWithCompletionHandler:handler]; + [(VZVirtualMachine *)machine pauseWithCompletionHandler:^(NSError *err) { + virtualMachineCompletionHandler(completionHandler, err); + }]; }); - Block_release(handler); } -void resumeWithCompletionHandler(void *machine, void *queue, const char *vmid) +void resumeWithCompletionHandler(void *machine, void *queue, void *completionHandler) { - handler_t handler = generateHandler(vmid, pauseHandler); dispatch_sync((dispatch_queue_t)queue, ^{ - [(VZVirtualMachine *)machine resumeWithCompletionHandler:handler]; + [(VZVirtualMachine *)machine resumeWithCompletionHandler:^(NSError *err) { + virtualMachineCompletionHandler(completionHandler, err); + }]; }); - Block_release(handler); } // TODO(codehex): use KVO @@ -903,6 +889,14 @@ bool vmCanRequestStop(void *machine, void *queue) } // --- TODO end +void sharedApplication() +{ + // Create a shared app instance. + // This will initialize the global variable + // 'NSApp' with the application instance. + [VZApplication sharedApplication]; +} + void startVirtualMachineWindow(void *machine, double width, double height) { @autoreleasepool { @@ -911,10 +905,6 @@ void startVirtualMachineWindow(void *machine, double width, double height) windowWidth:(CGFloat)width windowHeight:(CGFloat)height] autorelease]; - // Create a shared app instance. - // This will initialize the global variable - // 'NSApp' with the application instance. - [NSApplication sharedApplication]; NSApp.delegate = appDelegate; [NSApp run]; } From 09db4f37f208c37d8d9fe7b10d7f56cbab0c7afd Mon Sep 17 00:00:00 2001 From: Kei Kamikawa Date: Tue, 30 Aug 2022 01:31:18 +0900 Subject: [PATCH 3/5] implemented around stop --- virtualization.go | 24 ++++++++++++++++++++++++ virtualization.h | 2 ++ virtualization.m | 18 ++++++++++++++++++ 3 files changed, 44 insertions(+) diff --git a/virtualization.go b/virtualization.go index 6a7c05b..b94b044 100644 --- a/virtualization.go +++ b/virtualization.go @@ -46,6 +46,10 @@ const ( // VirtualMachineStateResuming The virtual machine is being resumed. // This is the intermediate state between VirtualMachineStatePaused and VirtualMachineStateRunning. VirtualMachineStateResuming + + // VZVirtualMachineStateStopping The virtual machine is being stopped. + // This is the intermediate state between VZVirtualMachineStateRunning and VZVirtualMachineStateStop. + VirtualMachineStateStopping ) // VirtualMachine represents the entire state of a single virtual machine. @@ -195,6 +199,11 @@ func (v *VirtualMachine) CanRequestStop() bool { return (bool)(C.vmCanRequestStop(v.Ptr(), v.dispatchQueue)) } +// CanStop returns whether the machine is in a state that can be stopped. +func (v *VirtualMachine) CanStop() bool { + return (bool)(C.vmCanStop(v.Ptr(), v.dispatchQueue)) +} + //export virtualMachineCompletionHandler func virtualMachineCompletionHandler(cgoHandlerPtr, errPtr unsafe.Pointer) { cgoHandler := *(*cgo.Handle)(cgoHandlerPtr) @@ -266,6 +275,21 @@ func (v *VirtualMachine) RequestStop() (bool, error) { return ret, nil } +// Stop stops a VM that’s in either a running or paused state. +// +// The completion handler returns an error object when the VM fails to stop, +// or nil if the stop was successful. +// +// Warning: This is a destructive operation. It stops the VM without +// giving the guest a chance to stop cleanly. +func (v *VirtualMachine) Stop(fn func(error)) { + h, done := makeHandler(fn) + handler := cgo.NewHandle(h) + defer handler.Delete() + C.stopWithCompletionHandler(v.Ptr(), v.dispatchQueue, unsafe.Pointer(&handler)) + <-done +} + // StartGraphicApplication starts an application to display graphics of the VM. // // You must to call runtime.LockOSThread before calling this method. diff --git a/virtualization.h b/virtualization.h index 0e71e75..c0ee359 100644 --- a/virtualization.h +++ b/virtualization.h @@ -105,10 +105,12 @@ bool requestStopVirtualMachine(void *machine, void *queue, void **error); void startWithCompletionHandler(void *machine, void *queue, void *completionHandler); void pauseWithCompletionHandler(void *machine, void *queue, void *completionHandler); void resumeWithCompletionHandler(void *machine, void *queue, void *completionHandler); +void stopWithCompletionHandler(void *machine, void *queue, void *completionHandler); bool vmCanStart(void *machine, void *queue); bool vmCanPause(void *machine, void *queue); bool vmCanResume(void *machine, void *queue); bool vmCanRequestStop(void *machine, void *queue); +bool vmCanStop(void *machine, void *queue); void *makeDispatchQueue(const char *label); diff --git a/virtualization.m b/virtualization.m index b1a9a8c..1785396 100644 --- a/virtualization.m +++ b/virtualization.m @@ -851,6 +851,15 @@ void resumeWithCompletionHandler(void *machine, void *queue, void *completionHan }); } +void stopWithCompletionHandler(void *machine, void *queue, void *completionHandler) +{ + dispatch_sync((dispatch_queue_t)queue, ^{ + [(VZVirtualMachine *)machine stopWithCompletionHandler:^(NSError *err) { + virtualMachineCompletionHandler(completionHandler, err); + }]; + }); +} + // TODO(codehex): use KVO bool vmCanStart(void *machine, void *queue) { @@ -887,6 +896,15 @@ bool vmCanRequestStop(void *machine, void *queue) }); return (bool)result; } + +bool vmCanStop(void *machine, void *queue) +{ + __block BOOL result; + dispatch_sync((dispatch_queue_t)queue, ^{ + result = ((VZVirtualMachine *)machine).canStop; + }); + return (bool)result; +} // --- TODO end void sharedApplication() From 2a17b22f29cb213ad9166c3a83dd3f5e03593039 Mon Sep 17 00:00:00 2001 From: Kei Kamikawa Date: Tue, 30 Aug 2022 01:33:01 +0900 Subject: [PATCH 4/5] fixed example for macOS --- example/macOS/main.go | 62 ++++++++++++++++++++++++++++--------------- 1 file changed, 40 insertions(+), 22 deletions(-) diff --git a/example/macOS/main.go b/example/macOS/main.go index d9504db..20117cb 100644 --- a/example/macOS/main.go +++ b/example/macOS/main.go @@ -6,9 +6,8 @@ import ( "fmt" "log" "os" - "os/signal" "runtime" - "syscall" + "time" "github.com/Code-Hex/vz/v2" ) @@ -48,9 +47,6 @@ func runVM(ctx context.Context) error { } vm := vz.NewVirtualMachine(config) - signalCh := make(chan os.Signal, 1) - signal.Notify(signalCh, syscall.SIGTERM) - errCh := make(chan error, 1) vm.Start(func(err error) { @@ -59,28 +55,50 @@ func runVM(ctx context.Context) error { } }) - vm.StartGraphicApplication(960, 600) + go func() { + for { + select { + case newState := <-vm.StateChangedNotify(): + if newState == vz.VirtualMachineStateRunning { + log.Println("start VM is running") + } + if newState == vz.VirtualMachineStateStopped || newState == vz.VirtualMachineStateStopping { + log.Println("stopped state") + errCh <- nil + return + } + case err := <-errCh: + errCh <- fmt.Errorf("failed to start vm: %w", err) + return + } + } + }() - for { - select { - case <-signalCh: + // cleanup is this function is useful when finished graphic application. + cleanup := func() { + for i := 1; vm.CanRequestStop(); i++ { result, err := vm.RequestStop() - if err != nil { - return err - } - log.Println("recieved signal", result) - case newState := <-vm.StateChangedNotify(): - if newState == vz.VirtualMachineStateRunning { - log.Println("start VM is running") - } - if newState == vz.VirtualMachineStateStopped { - log.Println("stopped successfully") - return nil + log.Printf("sent stop request(%d): %t, %v", i, result, err) + time.Sleep(time.Second * 3) + if i > 3 { + log.Println("call stop") + vm.Stop(func(err error) { + if err != nil { + log.Println("stop with error", err) + } + }) } - case err := <-errCh: - return fmt.Errorf("failed to start vm: %w", err) } + log.Println("finished cleanup") } + + runtime.LockOSThread() + vm.StartGraphicApplication(960, 600) + runtime.UnlockOSThread() + + cleanup() + + return <-errCh } func computeCPUCount() uint { From 51727eb0176d7583965f795ab9a00cba1e7fce91 Mon Sep 17 00:00:00 2001 From: Kei Kamikawa Date: Tue, 30 Aug 2022 01:37:00 +0900 Subject: [PATCH 5/5] fixed indents --- virtualization.m | 4 ++-- virtualization_view.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/virtualization.m b/virtualization.m index 1785396..9c8fc7f 100644 --- a/virtualization.m +++ b/virtualization.m @@ -909,9 +909,9 @@ bool vmCanStop(void *machine, void *queue) void sharedApplication() { - // Create a shared app instance. + // Create a shared app instance. // This will initialize the global variable - // 'NSApp' with the application instance. + // 'NSApp' with the application instance. [VZApplication sharedApplication]; } diff --git a/virtualization_view.h b/virtualization_view.h index 8e38912..36e16c4 100644 --- a/virtualization_view.h +++ b/virtualization_view.h @@ -12,7 +12,7 @@ @interface VZApplication : NSApplication { - bool shouldKeepRunning; + bool shouldKeepRunning; } - (void)run;