Opened 3 months ago

#1166 new Feature Request

MacOS special key handling + MacOS OpenGL swap buffer fixes + MacOS/Win/X11 clipboard support

Reported by: ventosus Owned by: dave
Priority: major Component: Pugl
Keywords: Cc:

Description

Kindly find attached two commits for pugl.

  1. MacOS special key handling + MacOS OpenGL swap buffer fixes
  2. MacOS/Win/X11 clipboard support
The following changes since commit adaf38d0984087ba9b6281dde46749dfb29a2a38:

  Fix cairo inclusion (2016-11-06 21:34:29 +0100)

are available in the git repository at:

  https://github.com/ventosus/pugl.git omk

for you to fetch changes up to 0406d71d421e141802ca8c085bbd00125a4af961:

  Implement clipboard (2017-03-07 19:49:51 +0100)

----------------------------------------------------------------
Hanspeter Portner (2):
      Implement special key handling on MacOS
      Implement clipboard

 pugl/pugl.h          |  12 +++++
 pugl/pugl.hpp        |   2 +
 pugl/pugl_internal.h |  32 ++++++++++++
 pugl/pugl_osx.m      | 138 ++++++++++++++++++++++++++++++++++++++++++++-------
 pugl/pugl_win.cpp    |  60 ++++++++++++++++++++++
 pugl/pugl_x11.c      |  90 +++++++++++++++++++++++++++++++++
 6 files changed, 317 insertions(+), 17 deletions(-)

diff --git a/pugl/pugl.h b/pugl/pugl.h
index 1b22260..dbbad90 100644
--- a/pugl/pugl.h
+++ b/pugl/pugl.h
@@ -569,6 +569,18 @@ PUGL_API void
 puglIgnoreKeyRepeat(PuglView* view, bool ignore);
 
 /**
+   Copy selection to clipboard.
+*/
+PUGL_API void
+puglCopyToClipboard(PuglView* view, const char* selection, size_t len);
+
+/**
+   Paste selection from clipboard.
+*/
+PUGL_API const char*
+puglPasteFromClipboard(PuglView* view, size_t* len);
+
+/**
    Grab the input focus.
 */
 PUGL_API void
diff --git a/pugl/pugl.hpp b/pugl/pugl.hpp
index 8232887..7d3ea9e 100644
--- a/pugl/pugl.hpp
+++ b/pugl/pugl.hpp
@@ -83,6 +83,8 @@ public:
 	virtual void*      getContext()                 { return puglGetContext(_view); }
 	virtual void       ignoreKeyRepeat(bool ignore) { puglIgnoreKeyRepeat(_view, ignore); }
 	virtual void       grabFocus()                  { puglGrabFocus(_view); }
+	virtual void       copyToClipboard(char* selection) { puglCopyToClipboard(_view, selection); }
+	virtual const char* pasteFromClipboard()        { return puglPasteFromClipboard(_view); }
 	virtual PuglStatus waitForEvent()               { return puglWaitForEvent(_view); }
 	virtual PuglStatus processEvents()              { return puglProcessEvents(_view); }
 	virtual void       postRedisplay()              { puglPostRedisplay(_view); }
diff --git a/pugl/pugl_internal.h b/pugl/pugl_internal.h
index 4a3fc0c..45084de 100644
--- a/pugl/pugl_internal.h
+++ b/pugl/pugl_internal.h
@@ -40,6 +40,7 @@ struct PuglViewImpl {
 	PuglEventFunc    eventFunc;
 
 	PuglInternals* impl;
+	char*          selection;
 
 	char*            windowClass;
 	PuglNativeWindow parent;
@@ -239,3 +240,34 @@ puglDispatchEvent(PuglView* view, const PuglEvent* event)
 		view->eventFunc(view, event);
 	}
 }
+
+static void
+puglClearSelection(PuglView* view)
+{
+	if(view->selection) {
+		free(view->selection);
+		view->selection = NULL;
+	}
+}
+
+static void
+puglSetSelection(PuglView* view, const char *selection, size_t len)
+{
+	puglClearSelection(view);
+
+	if(selection) {
+		view->selection = (char*)malloc(len + 1);
+		if(view->selection) {
+			memcpy(view->selection, selection, len);
+			view->selection[len] = 0;
+		}
+	}
+}
+
+static const char*
+puglGetSelection(PuglView* view, size_t* len)
+{
+	if(len)
+		*len = view->selection ? strlen(view->selection) : 0;
+	return view->selection;
+}
diff --git a/pugl/pugl_osx.m b/pugl/pugl_osx.m
index d2681cb..5f57798 100644
--- a/pugl/pugl_osx.m
+++ b/pugl/pugl_osx.m
@@ -33,6 +33,7 @@ struct PuglInternalsImpl {
 	PuglOpenGLView*  glview;
 	id               window;
 	NSEvent*         nextEvent;
+	unsigned         mods;
 #ifdef PUGL_HAVE_CAIRO
 	cairo_surface_t* surface;
 	cairo_t*         cr;
@@ -150,6 +151,9 @@ struct PuglInternalsImpl {
 		NSOpenGLPFAAccelerated,
 		NSOpenGLPFAColorSize, 32,
 		NSOpenGLPFADepthSize, 32,
+		NSOpenGLPFAMultisample,
+		NSOpenGLPFASampleBuffers, 1,
+		NSOpenGLPFASamples, 4,
 		0
 	};
 
@@ -166,6 +170,7 @@ struct PuglInternalsImpl {
 	if (self) {
 		[[self openGLContext] makeCurrentContext];
 		[self reshape];
+		[NSOpenGLContext clearCurrentContext];
 	}
 	return self;
 }
@@ -245,6 +250,39 @@ getModifiers(PuglView* view, NSEvent* ev)
 	return mods;
 }
 
+static PuglKey
+keySymToSpecial(PuglView* view, NSEvent* ev)
+{
+	NSString* chars = [ev charactersIgnoringModifiers];
+	if([chars length] == 1) {
+		switch ([chars characterAtIndex:0]) {
+		case NSF1FunctionKey:         return PUGL_KEY_F1;
+		case NSF2FunctionKey:         return PUGL_KEY_F2;
+		case NSF3FunctionKey:         return PUGL_KEY_F3;
+		case NSF4FunctionKey:         return PUGL_KEY_F4;
+		case NSF5FunctionKey:         return PUGL_KEY_F5;
+		case NSF6FunctionKey:         return PUGL_KEY_F6;
+		case NSF7FunctionKey:         return PUGL_KEY_F7;
+		case NSF8FunctionKey:         return PUGL_KEY_F8;
+		case NSF9FunctionKey:         return PUGL_KEY_F9;
+		case NSF10FunctionKey:        return PUGL_KEY_F10;
+		case NSF11FunctionKey:        return PUGL_KEY_F11;
+		case NSF12FunctionKey:        return PUGL_KEY_F12;
+		case NSLeftArrowFunctionKey:  return PUGL_KEY_LEFT;
+		case NSUpArrowFunctionKey:    return PUGL_KEY_UP;
+		case NSRightArrowFunctionKey: return PUGL_KEY_RIGHT;
+		case NSDownArrowFunctionKey:  return PUGL_KEY_DOWN;
+		case NSPageUpFunctionKey:     return PUGL_KEY_PAGE_UP;
+		case NSPageDownFunctionKey:   return PUGL_KEY_PAGE_DOWN;
+		case NSHomeFunctionKey:       return PUGL_KEY_HOME;
+		case NSEndFunctionKey:        return PUGL_KEY_END;
+		case NSInsertFunctionKey:     return PUGL_KEY_INSERT;
+		/* special keys (SHIFT, CTRL, ALT, SUPER) are handled in [flagsChanged] */
+		}
+	}
+	return (PuglKey)0;
+}
+
 -(void)updateTrackingAreas
 {
 	if (trackingArea != nil) {
@@ -415,7 +453,7 @@ getModifiers(PuglView* view, NSEvent* ev)
 		getModifiers(puglview, event),
 		[event keyCode],
 		puglDecodeUTF8((const uint8_t*)str),
-		0,  // TODO: Special keys?
+		keySymToSpecial(puglview, event),
 		{ 0, 0, 0, 0, 0, 0, 0, 0 },
 		false
 	};
@@ -441,7 +479,7 @@ getModifiers(PuglView* view, NSEvent* ev)
 		getModifiers(puglview, event),
 		[event keyCode],
 		puglDecodeUTF8((const uint8_t*)str),
-		0,  // TODO: Special keys?
+		keySymToSpecial(puglview, event),
 		{ 0, 0, 0, 0, 0, 0, 0, 0 },
 		false,
 	};
@@ -451,21 +489,47 @@ getModifiers(PuglView* view, NSEvent* ev)
 
 - (void) flagsChanged:(NSEvent*)event
 {
-	// TODO: Is this a sensible way to handle special keys?
-	/*
-		const unsigned mods = getModifiers(puglview, event);
-		if ((mods & PUGL_MOD_SHIFT) != (puglview->mods & PUGL_MOD_SHIFT)) {
-			puglview->specialFunc(puglview, mods & PUGL_MOD_SHIFT, PUGL_KEY_SHIFT);
-		} else if ((mods & PUGL_MOD_CTRL) != (puglview->mods & PUGL_MOD_CTRL)) {
-			puglview->specialFunc(puglview, mods & PUGL_MOD_CTRL, PUGL_KEY_CTRL);
-		} else if ((mods & PUGL_MOD_ALT) != (puglview->mods & PUGL_MOD_ALT)) {
-			puglview->specialFunc(puglview, mods & PUGL_MOD_ALT, PUGL_KEY_ALT);
-		} else if ((mods & PUGL_MOD_SUPER) != (puglview->mods & PUGL_MOD_SUPER)) {
-			puglview->specialFunc(puglview, mods & PUGL_MOD_SUPER, PUGL_KEY_SUPER);
-		}
-		puglview->mods = mods;
+	const unsigned mods = getModifiers(puglview, event);
+	PuglEventType type = PUGL_NOTHING;
+	PuglKey special = 0;
+
+	if ((mods & PUGL_MOD_SHIFT) != (puglview->impl->mods & PUGL_MOD_SHIFT)) {
+		type = mods & PUGL_MOD_SHIFT ? PUGL_KEY_PRESS : PUGL_KEY_RELEASE;
+		special = PUGL_KEY_SHIFT;
+	} else if ((mods & PUGL_MOD_CTRL) != (puglview->impl->mods & PUGL_MOD_CTRL)) {
+		type = mods & PUGL_MOD_CTRL ? PUGL_KEY_PRESS : PUGL_KEY_RELEASE;
+		special = PUGL_KEY_CTRL;
+	} else if ((mods & PUGL_MOD_ALT) != (puglview->impl->mods & PUGL_MOD_ALT)) {
+		type = mods & PUGL_MOD_ALT ? PUGL_KEY_PRESS : PUGL_KEY_RELEASE;
+		special = PUGL_KEY_ALT;
+	} else if ((mods & PUGL_MOD_SUPER) != (puglview->impl->mods & PUGL_MOD_SUPER)) {
+		type = mods & PUGL_MOD_SUPER ? PUGL_KEY_PRESS : PUGL_KEY_RELEASE;
+		special = PUGL_KEY_SUPER;
 	}
-	*/
+
+	if (special != 0) {
+		const NSPoint      wloc  = [self eventLocation:event];
+		const NSPoint      rloc  = [NSEvent mouseLocation];
+		PuglEventKey       ev    =  {
+			type,
+			puglview,
+			0,
+			[event timestamp],
+			wloc.x,
+			puglview->height - wloc.y,
+			rloc.x,
+			[[NSScreen mainScreen] frame].size.height - rloc.y,
+			mods,
+			[event keyCode],
+			0,
+			special,
+			{ 0, 0, 0, 0, 0, 0, 0, 0 },
+			false
+		};
+		puglDispatchEvent(puglview, (PuglEvent*)&ev);
+	}
+
+	puglview->impl->mods = mods;
 }
 
 @end
@@ -498,8 +562,11 @@ puglLeaveContext(PuglView* view, bool flush)
 #endif
 
 	if (flush) {
-		[[view->impl->glview openGLContext] flushBuffer];
+		//[[view->impl->glview openGLContext] flushBuffer];
+		glFlush();
+		glSwapAPPLE();
 	}
+	[NSOpenGLContext clearCurrentContext];
 }
 
 int
@@ -575,12 +642,49 @@ puglDestroy(PuglView* view)
 	if (view->impl->window) {
 		[view->impl->window release];
 	}
+	puglClearSelection(view);
 	free(view->windowClass);
 	free(view->impl);
 	free(view);
 }
 
 void
+puglCopyToClipboard(PuglView* view, const char* selection, size_t len)
+{
+	PuglInternals* const impl = view->impl;
+
+	puglSetSelection(view, selection, len);
+
+	NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
+	if(pasteboard) {
+		[pasteboard
+			declareTypes:[NSArray arrayWithObjects:NSStringPboardType, nil]
+			owner:nil];
+		[pasteboard
+			setString:[NSString stringWithUTF8String:puglGetSelection(view, NULL)]
+			forType:NSStringPboardType];
+	}
+}
+
+const char*
+puglPasteFromClipboard(PuglView* view, size_t* len)
+{
+	PuglInternals* const impl = view->impl;
+
+	NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
+	if(pasteboard && [ [pasteboard types] containsObject:NSStringPboardType] ) {
+		const NSString* mem = [pasteboard stringForType:NSStringPboardType];
+		if(mem) {
+			const char *utf8 = [mem UTF8String];
+			if(utf8)
+				puglSetSelection(view, utf8, strlen(utf8));
+		}
+	}
+
+	return puglGetSelection(view, len);
+}
+
+void
 puglGrabFocus(PuglView* view)
 {
 	// TODO
diff --git a/pugl/pugl_win.cpp b/pugl/pugl_win.cpp
index 83d4474..6468963 100644
--- a/pugl/pugl_win.cpp
+++ b/pugl/pugl_win.cpp
@@ -229,6 +229,7 @@ puglDestroy(PuglView* view)
 	ReleaseDC(view->impl->hwnd, view->impl->hdc);
 	DestroyWindow(view->impl->hwnd);
 	UnregisterClass(view->impl->wc.lpszClassName, NULL);
+	puglClearSelection(view);
 	free(view->windowClass);
 	free(view->impl);
 	free(view);
@@ -575,6 +576,65 @@ handleMessage(PuglView* view, UINT message, WPARAM wParam, LPARAM lParam)
 }
 
 void
+puglCopyToClipboard(PuglView* view, const char* selection, size_t len)
+{
+	PuglInternals* const impl = view->impl;
+
+	puglSetSelection(view, selection, len);
+
+	if(OpenClipboard(impl->hwnd)) {
+		const int wsize = MultiByteToWideChar(CP_UTF8, 0, selection, len, NULL, 0);
+		if(wsize) {
+			HGLOBAL mem = GlobalAlloc(GMEM_MOVEABLE, (wsize + 1) * sizeof(wchar_t));
+			if(mem) {
+				wchar_t* wstr = (wchar_t*)GlobalLock(mem);
+				if(wstr) {
+					MultiByteToWideChar(CP_UTF8, 0, selection, len, wstr, wsize);
+					wstr[wsize] = 0;
+					GlobalUnlock(mem);
+					SetClipboardData(CF_UNICODETEXT, mem);
+				}
+				else {
+					GlobalFree(mem);
+				}
+			}
+		}
+		CloseClipboard();
+	}
+}
+
+const char*
+puglPasteFromClipboard(PuglView* view, size_t* len)
+{
+	PuglInternals* const impl = view->impl;
+
+	if(IsClipboardFormatAvailable(CF_UNICODETEXT) && OpenClipboard(impl->hwnd)) {
+		HGLOBAL mem = GetClipboardData(CF_UNICODETEXT);
+		if(mem) {
+			const int wsize = GlobalSize(mem) / sizeof(wchar_t) - 1;
+			if(wsize) {
+				wchar_t* wstr = (wchar_t*)GlobalLock(mem);
+				if(wstr) {
+					const int utf8size = WideCharToMultiByte(CP_UTF8, 0, wstr, wsize, NULL, 0, NULL, NULL);
+					if(utf8size) {
+						char* utf8 = (char*)malloc(utf8size);
+						if(utf8) {
+							WideCharToMultiByte(CP_UTF8, 0, wstr, wsize, utf8, utf8size, NULL, NULL);
+							puglSetSelection(view, utf8, utf8size);
+							free(utf8);
+						}
+					}
+					GlobalUnlock(mem);
+				}
+			}
+		}
+		CloseClipboard();
+	}
+
+	return puglGetSelection(view, len);
+}
+
+void
 puglGrabFocus(PuglView* view)
 {
 	// TODO
diff --git a/pugl/pugl_x11.c b/pugl/pugl_x11.c
index 6a9135b..3d29ab3 100644
--- a/pugl/pugl_x11.c
+++ b/pugl/pugl_x11.c
@@ -23,6 +23,7 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#include <limits.h>
 
 #include <X11/Xatom.h>
 #include <X11/Xlib.h>
@@ -100,6 +101,8 @@ struct PuglInternalsImpl {
 #if defined(PUGL_HAVE_CAIRO) && defined(PUGL_HAVE_GL)
 	PuglCairoGL      cairo_gl;
 #endif
+	Atom             clipboard;
+	Atom             utf8_string;
 };
 
 PuglInternals*
@@ -268,6 +271,9 @@ puglCreateWindow(PuglView* view, const char* title)
 	Colormap cmap = XCreateColormap(
 		impl->display, xParent, vi->visual, AllocNone);
 
+	impl->clipboard = XInternAtom(impl->display, "CLIPBOARD", 0);
+	impl->utf8_string = XInternAtom(impl->display, "UTF8_STRING", 0);
+
 	XSetWindowAttributes attr;
 	memset(&attr, 0, sizeof(XSetWindowAttributes));
 	attr.colormap         = cmap;
@@ -283,6 +289,7 @@ puglCreateWindow(PuglView* view, const char* title)
 		CWColormap | CWEventMask, &attr);
 
 	if (!createContext(view, vi)) {
+		XFree(vi);
 		return 2;
 	}
 
@@ -369,6 +376,7 @@ puglDestroy(PuglView* view)
 		destroyContext(view);
 		XDestroyWindow(view->impl->display, view->impl->win);
 		XCloseDisplay(view->impl->display);
+		puglClearSelection(view);
 		free(view->windowClass);
 		free(view->impl);
 		free(view);
@@ -572,6 +580,39 @@ translateEvent(PuglView* view, XEvent xevent)
 		event.focus.grab = (xevent.xfocus.mode != NotifyNormal);
 		break;
 
+	case SelectionClear:
+		puglClearSelection(view);
+		break;
+	case SelectionRequest:
+	{
+		XSelectionEvent xev = {
+		 .type = SelectionNotify,
+		 .requestor = xevent.xselectionrequest.requestor,
+		 .selection = xevent.xselectionrequest.selection,
+		 .target = xevent.xselectionrequest.target,
+		 .time = xevent.xselectionrequest.time,
+		};
+
+		size_t len;
+		const char *selection = puglGetSelection(view, &len);
+		if(selection
+			&& (xevent.xselectionrequest.selection == view->impl->clipboard)
+			&& (xevent.xselectionrequest.target == view->impl->utf8_string) )
+		{
+			xev.property = xevent.xselectionrequest.property;
+			XChangeProperty(view->impl->display, xev.requestor,
+				xev.property, xev.target, 8, PropModeReplace,
+				(const uint8_t*)selection, len);
+		}
+		else // conversion failed
+		{
+			xev.property = None;
+		}
+		XSendEvent(view->impl->display, xev.requestor, True, 0, (XEvent*)&xev);
+
+		break;
+	}
+
 	default:
 		break;
 	}
@@ -580,6 +621,55 @@ translateEvent(PuglView* view, XEvent xevent)
 }
 
 void
+puglCopyToClipboard(PuglView* view, const char* selection, size_t len)
+{
+	PuglInternals* const impl = view->impl;
+
+	puglSetSelection(view, selection, len);
+
+	XSetSelectionOwner(impl->display, impl->clipboard, impl->win, CurrentTime);
+}
+
+const char*
+puglPasteFromClipboard(PuglView* view, size_t* len)
+{
+	PuglInternals* const impl = view->impl;
+
+	if(XGetSelectionOwner(impl->display, impl->clipboard) != impl->win) {
+		XConvertSelection(impl->display, impl->clipboard, impl->utf8_string,
+			XA_PRIMARY, impl->win, CurrentTime);
+
+		XEvent xevent;
+		while(!XCheckTypedWindowEvent(impl->display, impl->win, SelectionNotify,
+			&xevent)) {
+			// wait for answer
+		}
+
+		if(  (xevent.xselection.selection == impl->clipboard)
+			&& (xevent.xselection.target == impl->utf8_string)
+			&& (xevent.xselection.property == XA_PRIMARY) ) {
+			ulong nitems, rem;
+			int format;
+			uint8_t* data;
+			Atom type;
+
+			XGetWindowProperty(impl->display, impl->win, XA_PRIMARY,
+				0, LONG_MAX/4, False, AnyPropertyType, // request 32*32=1024 bytes
+				&type, &format, &nitems, &rem, &data);
+			if(data) {
+				if( (format == 8) && (type == impl->utf8_string) && (rem == 0) ) {
+					puglSetSelection(view, (const char*)data, nitems);
+				}
+
+				XFree(data);
+			}
+		}
+	}
+
+	return puglGetSelection(view, len);
+}
+
+void
 puglGrabFocus(PuglView* view)
 {
 	XSetInputFocus(

Change History (0)

Note: See TracTickets for help on using tickets.