arsd on Mac - solid progress

Posted 2023-09-25

Core D Development Statistics

In the community

Community announcements

See more at the announce forum.

Mac support in arsd

Last week, I decided to get an Apple Macintosh computer for my main office. This isn't my first time trying to use these, I previously had one of their all-in-one desktop units as well as a portable laptop model, both manufactured in 2011, but neither actually got me to do what I intended to do with them - work on the simpledisplay -version=OSXCocoa port. (The desktop unit was too big to keep set up here and I moved it to make space for more important things and it currently acts as a glorified dvd player upstairs, and the laptop model started beeping out failed RAM messages and it seems impossible to repair. Besides, both are old enough that it is hard to even run dmd on them anymore.)

This new unit, one of the Mac Mini models manufactured in 2014, I think is going to work for me. It is small enough and connections by ordinary cables to ordinary peripheral devices that I can stack it with my Windows box and put it on a KVM switch, meaning it doesn't take up any more space than I'm already using, practically speaking, so it remains convenient to access when I want it. Additionally, it seems to do a good job on power management, doing a kind of light sleep mode that keeps it accessible pretty quickly without eating a lot of electricity; since setting it up, it has consumed an average of about 2.3 W, ranging from about 2 W when sleeping to about 15 W when actively used, and around 5 W when I'm just sshed into it without activating its display. I can live with that.

Anyway, I got it set up Thursday night and did some extern(Objective-C) experimentation over the weekend, recreating parts of simpledisplay's support code. Last night, I got it drawing again, this time with more understanding of the code.

A bit of history

I did a public release of simpledisplay in April 2011 and shortly thereafter, a user on the D forum contributed an implementation of most the interface for Mac. This implementation used a lot of extern(C) objc_msgSend calls to define the necessary classes and call the associated methods to make things work. This implemented most the public interface at the time: window creation, basic drawing, character input, and the pulse timer, but I didn't understand the code and had no computer suitable for testing it, so I didn't keep up with it.

As Windows and Linux simpledisplay got enhanced keyboard and mouse support, the Mac version barely kept compiling. About four years later, I got that other Mac desktop computer, but I still didn't know the code. Instead, I switched the default build of simpledisplay on mac to use X11; XQuartz worked well on that computer and this solved some problems. At least something worked and it had most the features working thanks to the wonderful X Windowing System.

However, another couple years passed, and Apple decided to deprecate the XQuartz system. You could still download and install it separately (and indeed, I'm pretty sure you still can, though I haven't tried it on this new system yet), but you probably can't expect most users to do this. But, I also was gifted the Macbook Air, and used it to revive the old Cocoa code and at least got it compiling again - mostly by stubbing out unimplemented functions to throw a NotYetImplementedException instead of static asserting. At last you could do something with it. (It was this experience that I had in mind while writing this blog entry: http://dpldocs.info/this-week-in-d/Blog.Posted_2023_02_20.html#static-assert-patterns-arguably-harmful-for-porting about static assert considered harmful)

I also fixed some bugs in dmd's extern(Objective-C) code; by this point, I at least had some understanding of how the objc runtime worked and could compare output with Apple's compilers, but it was still kinda painful to use this... and, before long, that freebie computer died on me too anyway. That desktop still worked, but refused to update to run some of the new dmd compilers in addition to still being inconvenient to use, so eh, this got put on the wait list again.

I even tried doing a Mac VM, and had some success with it, but the VM was so hard to use - mouse capture and cursor sync was broken and the cpu consumption would randomly explode (I suspect that is some anti-piracy measure), so another general failure. I just deleted this VM last night, freeing up 50 GB on my main computer (which is getting full, so that's significant)!

The lesson is: to make this work, it needs to be easy for me. And I decided I'd spend a little bit of money this time (about $180 total), and thanks to Steven Schveighoffer for his advice in picking out appropriate hardware to achieve my varied goals.

Where we are now

simpledisplay's OSXCocoa implementation does work for basic animations, but the key and mouse events are bare minimum and most other things don't even exist. But, I can now read the existing code, and have started porting it to use dmd's extern(Objective-C) interface. This code is much nicer than it was even just a couple years ago - the bindings are convenient to write and easy to use.

There is one major downside though: it only works on dmd. On ldc, even the basics won't even compile; the feature is almost entirely unimplemented. On the other hand, pragma(linkerDirective) works on ldc to link in the objective-c framework libraries from the operating system, but this is unimplemented in dmd. Similarly, Apple has a new CPU in their recent models, as well as their iphone and ipad units (new and old), which has support on ldc but not on dmd. So, there is no D compiler that has full support for the breadth of Apple systems. dmd is as close as you get on the Intel cpu models, ldc is as close as you get on the others, but you sacrifice something either way.

I don't know the current status of any of these features on gdc; I've never tried gdc on mac.

Extending pragma(linkerDirective) to work for frameworks on dmd should be reasonably easy - the code has been written twice for a different named pragma, in a couple hundred lines, but never merged. Extending extern(Objective-C) to work on ldc is more work; the code in dmd is about 1,600 lines longer than what is present in ldc right now (see the file objc_glue.d in dmd's source). Much of that ought to be reusable in ldc - it does some ast glue to turn nice D function calls into uglier extern(C) objc runtime calls and I suspect this can, but some of it won't be, since it needs to handle linker sections and such. I don't think this is massively difficult, but to be honest, I don't know ldc's code well enough to be confident in that thought. To my knowledge, nobody has actually attempted to do this. (Indeed, it seems like only a few people have even worked on it on dmd, including me.)

I think both these projects would be worthwhile, and they're both on my own todo list.... and have been for years, still near the bottom of it. I might do it myself eventually, but it would be much better if someone else did.

BTW: the main reason why I don't want to do it is that setting up a ldc dev environment is a pain to me. But if that was already set up I might be able to do it.

How to interface with the objc runtime

In any case, it left me with a conundrum: do I use the much nicer extern(Objective-C) feature and have better code but less compiler compatibility, or do I keep up some of the extern(C) runtime calls for more compat but uglier code? Or a third option: like I did with arsd.jni, it would be possible to write some fairly nice code but then metaprogram the necessary glue in a library module that can work across compilers.

The extern(C) runtime calls are ugly and make writing the code much harder than it has to be. I'm not a Mac programmer, so I'd be both learning the necessary APIs and converting them to ugliness at the same time, and I frankly just don't want to.

Writing a metaprogram thing would work but... it would also be work, and I again just don't want to. It'd increase compile times, take a bunch of annoying effort, and it just seems silly to redo the very same things dmd already knows how to do. Even if I had that kind of time (and I really don't; simpledisplay on Mac would be nice to have but isn't terribly important so it must be done separately from my day job obligations and I only have so much spare time to spend on code beyond that at all anymore), it'd feel like a waste of that time.

If I'm going to spend a couple days implementing extern(Objective-C) in a library... I'd probably prefer to instead just implement it in ldc.

So my mind was made up: I'm going to abandon simpledisplay's existing extern(C) based code and rewrite it with extern(Objective-C), even if this means losing ldc support. Hopefully, ldc will pick up the feature too and then all will work again and joy can be found for D's mac/iphone/ipad users everywhere. But right now, I'm punting on it and just making my code work for me.

Where it is going

I already have my port drawing some things again and the framework is looking decent. One weird thing is the menu doesn't seem to work; the internet suggests this is a change in recent versions of the operating system, but I suspect there's something else going on. I don't know though, the items are in the menu, but they're all disabled and I don't know why.

Drawing, however, is going very well, and event handling should not take long now. Binding the apis has been quick and easy and I think I'll catch up on much of the functionality pretty soon this time.

I might even be able to get things like OperatingSystemFont working. Probably not things like drag and drop yet, but if OperatingSystemFont, the main event delegates, and ScreenPainter all work pretty completely, I should be able to get minigui's custom_widgets working too.

Obviously minigui would ideally use the native controls, but even just the custom widgets on the native drawing would be nice to have and works as a nice test case for some of the more obscure bits (indeed, many of the more obscure bits of simpledisplay were added to support minigui).

If all this works, I may even see about adding the simpleaudio and joystick modules to mac as well, which would let the arsd game things work (in theory at least).

Another interesting thing I'll have to consider is the arsd.core event loops. I still haven't integrated simpledisplay itself into that, and doing it on the Mac might require some changes. arsd.core already has a different concept for UI thread event loops and other worker thread loops, since this helps on Windows, and I suspect that will be necessary on Mac too. Previously, I assumed the FreeBSD kqueue implementation would transition straight over, but I'm not so sure now.

We'll see. This is why arsd.core's api is explicitly unstable - I'm reasonably happy with it, but the act of finishing these implementations and integrations are still likely to change things here.

Finally, simpledisplay will soon be getting some touch and drawing tablet events. I know how they work on Windows and Linux (and it is fairly different there), but adding the Mac to the list may change the plan somewhat. We'll see though, I don't know if i'll actually be able to test touch on this thing. I do have some old ipads but not sure if i'll be able to run any D code on them (well aside from the web, of course, but that doesn't help test the cocoa touch api implementation)! We'll see.

Some code

Let me show you the current cocoa test code. It has some objc bindings, a couple helpers, some class definitions, and a main.

I did encounter one compiler bug along the way: I can't have a reference to a ObjC object in a struct due to a missing init symbol. Should be an easy fix (the init should just be null!), but I just worked around it by wrapping the member in a union to work around it.

Otherwise, I thought it was fairly pleasant.

1 module main;
2 
3 extern (Objective-C) {
4 	import core.attribute : selector;
5 
6 	alias NSUInteger = size_t;
7 	alias NSInteger = ptrdiff_t;
8 	alias const(char)* SEL;
9 	alias void* id;
10 
11 	extern class NSObject {
12 		static NSObject alloc() @selector("alloc");
13 		void retain() @selector("retain");
14 		void release() @selector("release");
15 	}
16 
17 	extern class NSString : NSObject {
18 		override static NSString alloc() @selector("alloc");
19 
20 		NSString initWithUTF8String(const scope char* str) @selector("initWithUTF8String:");
21 
22 		NSString initWithBytes(
23 			const(ubyte)* bytes,
24 			NSUInteger length,
25 			NSStringEncoding encoding
26 		) @selector("initWithBytes:length:encoding:");
27 	}
28 
29 	extern class NSResponder : NSObject {
30 		NSMenu menu() @selector("menu");
31 		void menu(NSMenu menu) @selector("setMenu:");
32 	}
33 
34 	extern class NSApplication : NSResponder {
35 		static NSApplication shared_() @selector("sharedApplication");
36 
37 		NSApplicationDelegate delegate_() @selector("delegate");
38 		void delegate_(NSApplicationDelegate) @selector("setDelegate:");
39 
40 		bool setActivationPolicy(NSApplicationActivationPolicy activationPolicy) @selector("setActivationPolicy:");
41 
42 		void activateIgnoringOtherApps(bool flag) @selector("activateIgnoringOtherApps:");
43 
44 		@property NSMenu mainMenu() @selector("mainMenu");
45 		@property NSMenu mainMenu(NSMenu) @selector("setMainMenu:");
46 
47 		void run() @selector("run");
48 
49 		void terminate(void*) @selector("terminate:");
50 	}
51 
52 	extern interface NSApplicationDelegate {
53 		void applicationWillFinishLaunching(NSNotification notification) @selector("applicationWillFinishLaunching:");
54 		void applicationDidFinishLaunching(NSNotification notification) @selector("applicationDidFinishLaunching:");
55 		bool applicationShouldTerminateAfterLastWindowClosed(NSNotification notification) @selector("applicationShouldTerminateAfterLastWindowClosed:");
56 	}
57 
58 	extern class NSNotification : NSObject {
59 
60 	}
61 
62 	enum NSApplicationActivationPolicy : ptrdiff_t {
63 		/* The application is an ordinary app that appears in the Dock and may have a user interface.  This is the default for bundled apps, unless overridden in the Info.plist. */
64 		regular,
65 
66 		/* The application does not appear in the Dock and does not have a menu bar, but it may be activated programmatically or by clicking on one of its windows.  This corresponds to LSUIElement=1 in the Info.plist. */
67 		accessory,
68 
69 		/* The application does not appear in the Dock and may not create windows or be activated.  This corresponds to LSBackgroundOnly=1 in the Info.plist.  This is also the default for unbundled executables that do not have Info.plists. */
70 		prohibited
71 	}
72 
73 	extern class NSGraphicsContext : NSObject {
74 		static NSGraphicsContext currentContext() @selector("currentContext");
75 		NSGraphicsContext graphicsPort() @selector("graphicsPort");
76 	}
77 
78 	extern class NSMenu : NSObject {
79 		override static NSMenu alloc() @selector("alloc");
80 
81 		NSMenu init() @selector("init");
82 		NSMenu init(NSString title) @selector("initWithTitle:");
83 
84 		void setSubmenu(NSMenu menu, NSMenuItem item) @selector("setSubmenu:forItem:");
85 		void addItem(NSMenuItem newItem) @selector("addItem:");
86 
87 		NSMenuItem addItem(
88 			NSString title,
89 			SEL selector,
90 			NSString charCode
91 		) @selector("addItemWithTitle:action:keyEquivalent:");
92 	}
93 
94 	extern class NSMenuItem : NSObject {
95 		override static NSMenuItem alloc() @selector("alloc");
96 		NSMenuItem init() @selector("init");
97 
98 		NSMenuItem init(
99 			NSString title,
100 			SEL selector,
101 			NSString charCode
102 		) @selector("initWithTitle:action:keyEquivalent:");
103 
104 		void enabled(bool) @selector("setEnabled:");
105 
106 		NSResponder target(NSResponder) @selector("setTarget:");
107 	}
108 
109 	enum NSWindowStyleMask : size_t {
110 		borderless = 0,
111 		titled = 1 << 0,
112 		closable = 1 << 1,
113 		miniaturizable = 1 << 2,
114 		resizable	= 1 << 3,
115 
116 		/* Specifies a window with textured background. Textured windows generally don't draw a top border line under the titlebar/toolbar. To get that line, use the NSUnifiedTitleAndToolbarWindowMask mask.
117 		 */
118 		texturedBackground = 1 << 8,
119 
120 		/* Specifies a window whose titlebar and toolbar have a unified look - that is, a continuous background. Under the titlebar and toolbar a horizontal separator line will appear.
121 		 */
122 		unifiedTitleAndToolbar = 1 << 12,
123 
124 		/* When set, the window will appear full screen. This mask is automatically toggled when toggleFullScreen: is called.
125 		 */
126 		fullScreen = 1 << 14,
127 
128 		/* If set, the contentView will consume the full size of the window; it can be combined with other window style masks, but is only respected for windows with a titlebar.
129 		 Utilizing this mask opts-in to layer-backing. Utilize the contentLayoutRect or auto-layout contentLayoutGuide to layout views underneath the titlebar/toolbar area.
130 		 */
131 		fullSizeContentView = 1 << 15,
132 
133 		/* The following are only applicable for NSPanel (or a subclass thereof)
134 		 */
135 		utilityWindow			= 1 << 4,
136 		docModalWindow		 = 1 << 6,
137 		nonactivatingPanel		= 1 << 7, // Specifies that a panel that does not activate the owning application
138 		hUDWindow = 1 << 13 // Specifies a heads up display panel
139 	}
140 
141 	extern class NSWindow : NSObject {
142 		override static NSWindow alloc() @selector("alloc");
143 
144 		NSWindow init() @selector("init");
145 
146 		NSWindow initWithContentRect(
147 			NSRect contentRect,
148 			NSWindowStyleMask style,
149 			NSBackingStoreType bufferingType,
150 			bool flag
151 		) @selector("initWithContentRect:styleMask:backing:defer:");
152 
153 		void makeKeyAndOrderFront(id sender) @selector("makeKeyAndOrderFront:");
154 		NSView contentView() @selector("contentView");
155 		void orderFrontRegardless() @selector("orderFrontRegardless");
156 		void center() @selector("center");
157 		void contentView(NSView view) @selector("setContentView:");
158 
159 		NSString title() @selector("title");
160 		void title(NSString value) @selector("setTitle:");
161 	}
162 
163 	extern class NSView : NSObject {
164 		NSView init() @selector("init");
165 		NSView initWithFrame(NSRect frameRect) @selector("initWithFrame:");
166 
167 		void addSubview(NSView view) @selector("addSubview:");
168 
169 		bool wantsLayer() @selector("wantsLayer");
170 		void wantsLayer(bool value) @selector("setWantsLayer:");
171 
172 		CALayer layer() @selector("layer");
173 		void uiDelegate(NSObject) @selector("setUIDelegate:");
174 
175 		void drawRect(NSRect rect) @selector("drawRect:");
176 		bool isFlipped() @selector("isFlipped");
177 		bool acceptsFirstResponder() @selector("acceptsFirstResponder");
178 		bool setNeedsDisplay(bool) @selector("setNeedsDisplay:");
179 	}
180 
181 
182 	extern class NSColor : NSObject {
183 		override static NSColor alloc() @selector("alloc");
184 		static NSColor redColor() @selector("redColor");
185 
186 		CGColorRef CGColor() @selector("CGColor");
187 	}
188 
189 	extern class CALayer : NSObject {
190 		CGFloat borderWidth() @selector("borderWidth");
191 		void borderWidth(CGFloat value) @selector("setBorderWidth:");
192 
193 		CGColorRef borderColor() @selector("borderColor");
194 		void borderColor(CGColorRef) @selector("setBorderColor:");
195 	}
196 
197 
198 	extern class NSViewController : NSObject {
199 		NSView view() @selector("view");
200 		void view(NSView view) @selector("setView:");
201 	}
202 
203 	enum NSBackingStoreType : size_t {
204 		retained = 0,
205 		nonretained = 1,
206 		buffered = 2
207 	}
208 
209 	enum NSStringEncoding : NSUInteger {
210 		NSASCIIStringEncoding = 1,		/* 0..127 only */
211 		NSUTF8StringEncoding = 4,
212 		NSUnicodeStringEncoding = 10,
213 
214 		NSUTF16StringEncoding = NSUnicodeStringEncoding,
215 		NSUTF16BigEndianStringEncoding = 0x90000100,
216 		NSUTF16LittleEndianStringEncoding = 0x94000100,
217 		NSUTF32StringEncoding = 0x8c000100,
218 		NSUTF32BigEndianStringEncoding = 0x98000100,
219 		NSUTF32LittleEndianStringEncoding = 0x9c000100
220 	}
221 
222 
223 	struct CGColor;
224 	alias CGColorRef = CGColor*;
225 
226 	alias CGFloat = double;
227 
228 	struct NSPoint {
229 		CGFloat x;
230 		CGFloat y;
231 	}
232 
233 	struct NSSize {
234 		CGFloat width;
235 		CGFloat height;
236 	}
237 
238 	struct NSRect {
239 		NSPoint origin;
240 		NSSize size;
241 	}
242 
243 	alias NSPoint CGPoint;
244 	alias NSSize CGSize;
245 	alias NSRect CGRect;
246 
247 	pragma(inline, true) NSPoint NSMakePoint(CGFloat x, CGFloat y) {
248 		NSPoint p;
249 		p.x = x;
250 		p.y = y;
251 		return p;
252 	}
253 
254 	pragma(inline, true) NSSize NSMakeSize(CGFloat w, CGFloat h) {
255 		NSSize s;
256 		s.width = w;
257 		s.height = h;
258 		return s;
259 	}
260 
261 	pragma(inline, true) NSRect NSMakeRect(CGFloat x, CGFloat y, CGFloat w, CGFloat h) {
262 		NSRect r;
263 		r.origin.x = x;
264 		r.origin.y = y;
265 		r.size.width = w;
266 		r.size.height = h;
267 		return r;
268 	}
269 
270 
271 }
272 
273 // helper raii refcount object
274 struct MacString {
275 	union {
276 		// must be wrapped cuz of bug in dmd
277 		// referencing an init symbol when it should
278 		// just be null. but the union makes it work
279 		NSString s;
280 	}
281 
282 	// FIXME: if a string literal it would be kinda nice to use
283 	// the other function. but meh
284 
285 	this(scope const char[] str) {
286 		this.s = NSString.alloc.initWithBytes(
287 			cast(const(ubyte)*) str.ptr,
288 			str.length,
289 			NSStringEncoding.NSUTF8StringEncoding
290 		);
291 	}
292 
293 	NSString borrow() {
294 		return s;
295 	}
296 
297 	this(this) {
298 		if(s !is null)
299 			s.retain();
300 	}
301 
302 	~this() {
303 		if(s !is null) {
304 			s.release();
305 			s = null;
306 		}
307 	}
308 }
309 
310 extern(C) {
311 	enum CGPathDrawingMode {
312 		kCGPathFill,
313 		kCGPathEOFill,
314 		kCGPathStroke,
315 		kCGPathFillStroke,
316 		kCGPathEOFillStroke
317 	}
318 
319 	alias NSGraphicsContext CGContextRef;
320 
321 	void CGContextSetRGBFillColor(CGContextRef c, double red, double green, double blue, double alpha);
322 
323 	void CGContextBeginPath(CGContextRef c);
324 	void CGContextDrawPath(CGContextRef c, CGPathDrawingMode mode);
325 	void CGContextAddEllipseInRect(CGContextRef c, CGRect rect);
326 	void CGContextAddArc(CGContextRef c, double x, double y, double radius, double startAngle, double endAngle, long clockwise);
327 	void CGContextAddRect(CGContextRef c, CGRect rect);
328 }
329 
330 extern (C) void NSLog(NSString, ...);
331 
332 	extern (Objective-C)
333 	__gshared NSApplication NSApp_;
334 
335 	NSApplication NSApp() {
336 		if(NSApp_ is null)
337 			NSApp_ = NSApplication.shared_;
338 		return NSApp_;
339 	}
340 
341 
342 /+ +++++++++++++++++++++++++ +/
343 
344 extern(Objective-C)
345 class AppDelegate : NSObject, NSApplicationDelegate {
346 	override static AppDelegate alloc() @selector("alloc");
347 
348 	override void applicationWillFinishLaunching(NSNotification notification) @selector("applicationWillFinishLaunching:") {
349 		immutable style = NSWindowStyleMask.resizable |
350 			NSWindowStyleMask.closable |
351 			NSWindowStyleMask.miniaturizable |
352 			NSWindowStyleMask.titled;
353 
354 		NSMenu mainMenu = NSMenu.alloc.init(MacString("Main").borrow);
355 
356 		{
357 			auto item = mainMenu.addItem(MacString("Test").borrow, null, MacString("").borrow);
358 			auto menu = NSMenu.alloc.init(MacString("Test2").borrow);
359 			mainMenu.setSubmenu(menu, item);
360 
361 			auto newItem = menu.addItem(MacString("Quit").borrow, "terminate:", MacString("q").borrow);
362 			newItem.target = NSApp;
363 			auto newItem2 = menu.addItem(MacString("Idk").borrow, "quit:", MacString("x").borrow);
364 			newItem2.target = NSApp;
365 		}
366 
367 		{
368 			auto item = mainMenu.addItem(MacString("Test3").borrow, null, MacString("").borrow);
369 			auto menu = NSMenu.alloc.init(MacString("Test4").borrow); // this is the title actually used
370 			mainMenu.setSubmenu(menu, item);
371 
372 			auto newItem = menu.addItem(MacString("Quit2").borrow, "stop:", MacString("s").borrow);
373 			auto newItem2 = menu.addItem(MacString("Idk2").borrow, "quit:", MacString("b").borrow);
374 		}
375 
376 
377 		NSApp.menu = mainMenu;
378 
379 
380 		auto window = NSWindow.alloc.initWithContentRect(
381 			NSMakeRect(10, 10, 300, 300),
382 			style,
383 			NSBackingStoreType.buffered,
384 			false
385 		);
386 
387 		window.title = MacString("D Rox").borrow;
388 
389 		// auto controller = ViewController.alloc.init;
390 		auto view = SDGraphicsView.alloc.init;
391 		window.contentView = view;
392 
393 		window.center();
394 
395 		/+
396 		this.window = window;
397 		this.controller = controller;
398 		+/
399 
400 		window.makeKeyAndOrderFront(null);
401 	}
402 
403 	override void applicationDidFinishLaunching(NSNotification notification) @selector("applicationDidFinishLaunching:") {
404 		NSApplication.shared_.activateIgnoringOtherApps(true);
405 	}
406 	override bool applicationShouldTerminateAfterLastWindowClosed(NSNotification notification) @selector("applicationShouldTerminateAfterLastWindowClosed:") {
407 		return true;
408 	}
409 }
410 
411 extern(Objective-C)
412 class SDGraphicsView : NSView {
413 	override static SDGraphicsView alloc() @selector("alloc");
414 
415 	override void drawRect(NSRect rect) @selector("drawRect:") {
416 
417 		auto context = NSGraphicsContext.currentContext.graphicsPort;
418 
419 	    import arsd.color;
420 	    auto color = Color.red;
421             CGContextSetRGBFillColor(context, color.r/255.0f, color.g/255.0f, color.b/255.0f, color.a/255.0f);
422 
423 		NSLog(MacString("here").borrow);
424 
425             CGContextBeginPath(context);
426             //auto rect2 = CGRect(CGPoint(30, 30), CGSize(90, 90));
427             CGContextAddRect(context, rect);
428             CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke);
429 
430 	}
431 
432 	override bool isFlipped() @selector("isFlipped") {
433 		return true;
434 	}
435 	override bool acceptsFirstResponder() @selector("acceptsFirstResponder") {
436 		return true;
437 	}
438 
439 	// SimpleWindow simpleWindow;
440 	void simpledisplay_pulse() @selector("simpledisplay_pulse") {
441 		setNeedsDisplay = true;
442 	}
443 }
444 
445 void main() {
446 	auto dg = AppDelegate.alloc;
447 	NSApp.delegate_ = dg;
448 
449 	NSApp.setActivationPolicy(NSApplicationActivationPolicy.regular);
450 
451 	// auto view = SDGraphicsView.alloc;
452 
453 	NSLog(MacString("Hello there").borrow);
454 	NSApp.run();
455 	NSLog(MacString("Bye").borrow);
456 }

Aside: my dev env

On the mac, I wanted it convenient to use, so I did a minimal vim thing that puts dmdi % on F3 and runcurrent % on F1. Meaning it will do a dmd -i currentfile.d when I hit that key and then run the program if i hit the other, so I rarely need to actually go to the other keyboard. That's actually kinda nice. I should try running gui apps on Windows through ssh too and see if it works similarly. It is weird, X is better, but this is useful in its own way.

It also rsync's the arsd folder to the mac on every build. This adds some lag to each built but also means there's a single source of those files without bothering with git nonsense or making a shared drive work or anything else. I just edit them on my main computer and hit the magic key and see the difference. It makes working on this side computer far more convenient.

And the way the mac wakes up when i ssh it and goes to sleep when i leave it automatically means it *almost* feels like just an extension of my regular computer. Let's hope its RAM doesn't die on me anytime soon lol.

References

https://github.com/dlang/dmd/pull/10615/files