Skip to content

Commit

Permalink
Using a tool to handle the clipboard does not work with P9.
Browse files Browse the repository at this point in the history
These tools are implemented by sending a virtual paste event.
This is usually implemented by sending a virtual Ctrl/Cmd + V event.

These events are handled by SDL, but they are handled by sending three events.

We need to correctly detect this sequence and handling the event.
  • Loading branch information
tesonep committed Jul 9, 2021
1 parent 75139f5 commit 13b3e17
Show file tree
Hide file tree
Showing 10 changed files with 275 additions and 30 deletions.
58 changes: 29 additions & 29 deletions src/OSWindow-SDL2/OSSDL2Driver.class.st
Expand Up @@ -7,7 +7,8 @@ Class {
#instVars : [
'inputSemaphore',
'eventQueue',
'globalListeners'
'globalListeners',
'eventFilter'
],
#classVars : [
'EventLoopProcess',
Expand Down Expand Up @@ -66,22 +67,6 @@ OSSDL2Driver >> closeRemovedJoystick: index [
self traceCr: ('Joystick <1s> removed' expandMacrosWith: index)
]

{ #category : #'events-processing' }
OSSDL2Driver >> convertEvent: sdlEvent [
| mappedEvent window |

mappedEvent := sdlEvent mapped ifNil: [ ^ nil ].
mappedEvent windowID ifNil: [ ^ self sendEventWithoutWindow: mappedEvent ].

WindowMapMutex critical: [
window := WindowMap at: mappedEvent windowID ifAbsent: [ ^ nil ].
].

^ window handleNewSDLEvent: mappedEvent.


]

{ #category : #'window creation and deletion' }
OSSDL2Driver >> createWindowWithAttributes: attributes osWindow: osWindow [
| flags handle aBackendWindow glAttributes x y |
Expand Down Expand Up @@ -134,17 +119,33 @@ OSSDL2Driver >> createWindowWithAttributes: attributes osWindow: osWindow [
]

{ #category : #'events-processing' }
OSSDL2Driver >> evaluateUserInterrupt: anSDLEvent [
OSSDL2Driver >> dispatchEvent: mappedEvent [

| window |

anSDLEvent ifNil: [ ^ self ].
mappedEvent windowID ifNil: [ ^ self sendEventWithoutWindow: mappedEvent ].

WindowMapMutex critical: [
window := WindowMap at: mappedEvent windowID ifAbsent: [ ^ nil ] ].

^ window handleNewSDLEvent: mappedEvent.


]

{ #category : #'events-processing' }
OSSDL2Driver >> evaluateUserInterrupt: anSDLEvent [

((anSDLEvent isKindOf: SDL_KeyDownEvent)
and: [ ((anSDLEvent keysym mod bitAnd: 16r200) = 0) "ignore all if AltGr is pressed"
and: [(anSDLEvent keysym mod anyMask: 1344)
and: [ anSDLEvent keysym scancode = 55 ] ]])
anSDLEvent isUserInterrupt
ifTrue: [ UserInterruptHandler new handleUserInterrupt ]
]

{ #category : #accessing }
OSSDL2Driver >> eventFilter [

^ eventFilter ifNil: [ eventFilter := OSSDLPasteEventFilter new next: self; yourself ]
]

{ #category : #'events-processing' }
OSSDL2Driver >> eventLoopProcessWithPlugin [
| event session semaIndex |
Expand Down Expand Up @@ -197,7 +198,6 @@ OSSDL2Driver >> eventLoopProcessWithoutPlugin [
{ #category : #initialization }
OSSDL2Driver >> initialize [
inputSemaphore := Semaphore new.
eventQueue := AtomicSharedQueue new.
globalListeners := WeakSet new.
self
initializeWindowMap;
Expand Down Expand Up @@ -268,12 +268,12 @@ OSSDL2Driver >> primitiveSetVMSDL2Input: semaIndex [

{ #category : #'events-processing' }
OSSDL2Driver >> processEvent: sdlEvent [
| event |
| mappedEvent |

[
event := self convertEvent: sdlEvent.
self evaluateUserInterrupt: sdlEvent mapped.
event ifNotNil: [ eventQueue nextPut: event ].
[
mappedEvent := sdlEvent mapped.
self evaluateUserInterrupt: mappedEvent.
self eventFilter dispatchEvent: mappedEvent
] on: Error do: [ :err |
"It is critical, that event handling keep running despite errors.
Normally, any errors in event handling requires immediate attention and fixing"
Expand Down
155 changes: 155 additions & 0 deletions src/OSWindow-SDL2/OSSDLPasteEventFilter.class.st
@@ -0,0 +1,155 @@
Class {
#name : #OSSDLPasteEventFilter,
#superclass : #Object,
#instVars : [
'next',
'lastEvents',
'currentState'
],
#pools : [
'SDL2Constants'
],
#category : #'OSWindow-SDL2-Base'
}

{ #category : #'last events' }
OSSDLPasteEventFilter >> addEvent: aEvent [

self lastEvents add: aEvent
]

{ #category : #'events-processing' }
OSSDLPasteEventFilter >> changeEventsToIncludeModifier [

| modifierToUse defaultModifier |

"We modify the KeyUp/KeyDown events to include the correct modifier.
This is because tools to handle copy/paste use virtual events, and SDL is dropping the modifier.
The correct modifier is calculated depending of the current platform modifier"

modifierToUse := 0.
defaultModifier := OSPlatform current defaultModifier.

defaultModifier = KMModifier command ifTrue: [ modifierToUse := KMOD_GUI ].
defaultModifier = KMModifier alt ifTrue: [ modifierToUse := KMOD_LALT ].
defaultModifier = KMModifier ctrl ifTrue: [ modifierToUse := KMOD_LCTRL ].

self lastEvents do: [ :anEvent |
(anEvent isKeyUpEvent or: [ anEvent isKeyDownEvent ])
ifTrue: [ anEvent keysym mod: (anEvent keysym mod bitOr: modifierToUse) ] ]
]

{ #category : #accessing }
OSSDLPasteEventFilter >> currentState [

^ currentState ifNil: [ currentState := 1 ]
]

{ #category : #accessing }
OSSDLPasteEventFilter >> currentState: aValue [

currentState := aValue
]

{ #category : #'events-processing' }
OSSDLPasteEventFilter >> dispatchEvent: aSDLEvent [

"This filter will handle the case when an external Clipboard handling tool is used.
When an external tool modifies the clipboard and send a virtual paste command, it sends three events:
1) SDL_CLIPBOARDUPDATEEVENT
2) SDL_KEYDOWN with $v as the key
3) SDL KEYUP with $v as the keysym
If we find this three events, we need to modify the last two events to include defaul system modifier.
Check #changeEventsToIncludeModifier.
This is implemented with a 3 state machine.
State 1: This is the default state. All events are passed to the next filter (or the driver if there is not filter).
If we found a Clipboard Update event, we store the event for the future, and change the state to 2. We don't dispatch the event.
State 2: It will check if the event is a KeyDown with a $V and if it is, it will store the event and change to state 3.
If the event is other one, it will go to error state and flush all pending events, change to state one and dispatch current event.
State 3: It will check if the event is a KeyUp with a $V and if it is, it will update pending events to have the virtual modifier.
Then pending events are flushed.
If the event is other one, it will go to error state and flush all pending events, change to state one and dispatch current event. "

(self currentState = 1 and: [aSDLEvent isClipboardUpdateEvent not])
ifTrue: [ ^ next dispatchEvent: aSDLEvent ].

(self currentState = 1 and: [aSDLEvent isClipboardUpdateEvent])
ifTrue: [
self addEvent: aSDLEvent.
self currentState: 2.
^ self ].

(self currentState = 1 and: [aSDLEvent isClipboardUpdateEvent])
ifTrue: [
self addEvent: aSDLEvent.
self currentState: 2.
^ self ].

(self currentState = 2 and: [self isKeyDownV: aSDLEvent])
ifTrue: [
self addEvent: aSDLEvent.
self currentState: 3.
^ self ].

(self currentState = 3 and: [self isKeyUpV: aSDLEvent])
ifTrue: [
self addEvent: aSDLEvent.
self changeEventsToIncludeModifier.
self flushEvents.
self currentState: 1.
^ self].

self flushEvents.
self currentState: 1.
^ next dispatchEvent: aSDLEvent
]

{ #category : #'last events' }
OSSDLPasteEventFilter >> flushEvents [

self lastEvents do: [ :anEvent | next dispatchEvent: anEvent ].
self lastEvents removeAll.
]

{ #category : #'events-processing' }
OSSDLPasteEventFilter >> isKeyDownV: aSDLEvent [

^ aSDLEvent isKeyDownEvent
and: [ aSDLEvent keysym scancode = 25 "SDL_SCANCODE_V"
and: [aSDLEvent keysym sym = SDLK_v]]

]

{ #category : #'events-processing' }
OSSDLPasteEventFilter >> isKeyUpV: aSDLEvent [

^ aSDLEvent isKeyUpEvent
and: [ aSDLEvent keysym scancode = 25 "SDL_SCANCODE_V"
and: [aSDLEvent keysym sym = SDLK_v]]

]

{ #category : #'last events' }
OSSDLPasteEventFilter >> lastEvents [

^ lastEvents ifNil: [ lastEvents := OrderedCollection new ]
]

{ #category : #accessing }
OSSDLPasteEventFilter >> next [

^ next
]

{ #category : #accessing }
OSSDLPasteEventFilter >> next: anObject [

next := anObject
]
27 changes: 26 additions & 1 deletion src/OSWindow-SDL2/SDL2MappedEvent.class.st
Expand Up @@ -19,14 +19,39 @@ SDL2MappedEvent class >> eventType [

{ #category : #'instance creation' }
SDL2MappedEvent class >> fromSdlEvent: event [
^ self new setHandle: event getHandle
"SDL event handlers are copied because there are allocated in Pharo memory as a bytearray"
^ self new setHandle: event getHandle copy
]

{ #category : #'event type' }
SDL2MappedEvent >> eventType [
^ nil
]

{ #category : #testing }
SDL2MappedEvent >> isClipboardUpdateEvent [

^ false
]

{ #category : #testing }
SDL2MappedEvent >> isKeyDownEvent [

^ false
]

{ #category : #testing }
SDL2MappedEvent >> isKeyUpEvent [

^ false
]

{ #category : #testing }
SDL2MappedEvent >> isUserInterrupt [

^ false
]

{ #category : #accessing }
SDL2MappedEvent >> windowID [
^ nil
Expand Down
20 changes: 20 additions & 0 deletions src/OSWindow-SDL2/SDL_ClipboardUpdateEvent.class.st
@@ -0,0 +1,20 @@
"
I am an event that is generated when the clipboard has been updated by other application.
"
Class {
#name : #'SDL_ClipboardUpdateEvent',
#superclass : #'SDL_CommonEvent',
#category : #'OSWindow-SDL2-Bindings'
}

{ #category : #'event type' }
SDL_ClipboardUpdateEvent class >> eventType [

^ SDL_CLIPBOARDUPDATE
]

{ #category : #testing }
SDL_ClipboardUpdateEvent >> isClipboardUpdateEvent [

^ true
]
7 changes: 7 additions & 0 deletions src/OSWindow-SDL2/SDL_CommonEvent.class.st
Expand Up @@ -27,6 +27,13 @@ SDL_CommonEvent >> accept: aVisitor [
^ aVisitor visitCommonEvent: self
]

{ #category : #'accessing - structure variables' }
SDL_CommonEvent >> printOn: aStream [

super printOn: aStream.
aStream print: { self timestamp }.
]

{ #category : #'accessing - structure variables' }
SDL_CommonEvent >> timestamp [
"This method was automatically generated"
Expand Down
2 changes: 2 additions & 0 deletions src/OSWindow-SDL2/SDL_Event.class.st
Expand Up @@ -37,6 +37,8 @@ SDL_Event >> initialize [

{ #category : #accessing }
SDL_Event >> mapped [
"This method converts a raw event in a mapped event.
It should always return an event. Nil is not a valid return object"
^ (EventTypeMap at: self type ifAbsent: [ ^ self unknownEvent ]) fromSdlEvent: self
]

Expand Down
15 changes: 15 additions & 0 deletions src/OSWindow-SDL2/SDL_KeyDownEvent.class.st
Expand Up @@ -17,6 +17,21 @@ SDL_KeyDownEvent >> accept: aVisitor [
^ aVisitor visitKeyDownEvent: self
]

{ #category : #testing }
SDL_KeyDownEvent >> isKeyDownEvent [

^ true
]

{ #category : #testing }
SDL_KeyDownEvent >> isUserInterrupt [

^ ((self keysym mod bitAnd: 16r200) = 0) "ignore all if AltGr is pressed"
and: [(self keysym mod anyMask: 1344)
and: [ self keysym scancode = 55 ] ]

]

{ #category : #'accessing - structure variables' }
SDL_KeyDownEvent >> keysym [
"This method was automatically generated"
Expand Down
6 changes: 6 additions & 0 deletions src/OSWindow-SDL2/SDL_KeyUpEvent.class.st
Expand Up @@ -17,6 +17,12 @@ SDL_KeyUpEvent >> accept: aVisitor [
^ aVisitor visitKeyUpEvent: self
]

{ #category : #testing }
SDL_KeyUpEvent >> isKeyUpEvent [

^ true
]

{ #category : #'accessing - structure variables' }
SDL_KeyUpEvent >> keysym [
"This method was automatically generated"
Expand Down
8 changes: 8 additions & 0 deletions src/OSWindow-SDL2/SDL_KeyboardEvent.class.st
Expand Up @@ -70,6 +70,14 @@ SDL_KeyboardEvent >> padding3: anObject [
handle unsignedByteAt: OFFSET_PADDING3 put: anObject
]

{ #category : #printing }
SDL_KeyboardEvent >> printOn: aStream [

super printOn: aStream.

aStream print: { self timestamp. self keysym. self type. self windowID. self state }.
]

{ #category : #'accessing - structure variables' }
SDL_KeyboardEvent >> repeat [
"This method was automatically generated"
Expand Down

0 comments on commit 13b3e17

Please sign in to comment.