1 /*
2   Simple DirectMedia Layer
3   Copyright (C) 1997-2016 Sam Lantinga <slouken@libsdl.org>
4 
5   This software is provided 'as-is', without any express or implied
6   warranty.  In no event will the authors be held liable for any damages
7   arising from the use of this software.
8 
9   Permission is granted to anyone to use this software for any purpose,
10   including commercial applications, and to alter it and redistribute it
11   freely, subject to the following restrictions:
12 
13   1. The origin of this software must not be misrepresented; you must not
14      claim that you wrote the original software. If you use this software
15      in a product, an acknowledgment in the product documentation would be
16      appreciated but is not required.
17   2. Altered source versions must be plainly marked as such, and must not be
18      misrepresented as being the original software.
19   3. This notice may not be removed or altered from any source distribution.
20 */
21 
22 #ifndef _SDL_BWin_h
23 #define _SDL_BWin_h
24 
25 #ifdef __cplusplus
26 extern "C" {
27 #endif
28 
29 #include "../../SDL_internal.h"
30 #include "SDL.h"
31 #include "SDL_syswm.h"
32 #include "SDL_bframebuffer.h"
33 
34 #ifdef __cplusplus
35 }
36 #endif
37 
38 #include <stdio.h>
39 #include <AppKit.h>
40 #include <InterfaceKit.h>
41 #include <be/game/DirectWindow.h>
42 #if SDL_VIDEO_OPENGL
43 #include <be/opengl/GLView.h>
44 #endif
45 #include "SDL_events.h"
46 #include "../../main/haiku/SDL_BApp.h"
47 
48 
49 enum WinCommands {
50     BWIN_MOVE_WINDOW,
51     BWIN_RESIZE_WINDOW,
52     BWIN_SHOW_WINDOW,
53     BWIN_HIDE_WINDOW,
54     BWIN_MAXIMIZE_WINDOW,
55     BWIN_MINIMIZE_WINDOW,
56     BWIN_RESTORE_WINDOW,
57     BWIN_SET_TITLE,
58     BWIN_SET_BORDERED,
59     BWIN_SET_RESIZABLE,
60     BWIN_FULLSCREEN
61 };
62 
63 
64 class SDL_BWin:public BDirectWindow
65 {
66   public:
67     /* Constructor/Destructor */
SDL_BWin(BRect bounds,window_look look,uint32 flags)68     SDL_BWin(BRect bounds, window_look look, uint32 flags)
69         : BDirectWindow(bounds, "Untitled", look, B_NORMAL_WINDOW_FEEL, flags)
70     {
71         _last_buttons = 0;
72 
73 #if SDL_VIDEO_OPENGL
74         _SDL_GLView = NULL;
75 #endif
76         _shown = false;
77         _inhibit_resize = false;
78         _mouse_focused = false;
79         _prev_frame = NULL;
80 
81         /* Handle framebuffer stuff */
82         _connected = _connection_disabled = false;
83         _buffer_created = _buffer_dirty = false;
84         _trash_window_buffer = false;
85         _buffer_locker = new BLocker();
86         _bitmap = NULL;
87         _clips = NULL;
88 
89 #ifdef DRAWTHREAD
90         _draw_thread_id = spawn_thread(BE_DrawThread, "drawing_thread",
91                             B_NORMAL_PRIORITY, (void*) this);
92         resume_thread(_draw_thread_id);
93 #endif
94     }
95 
~SDL_BWin()96     virtual ~ SDL_BWin()
97     {
98         Lock();
99         _connection_disabled = true;
100         int32 result;
101 
102 #if SDL_VIDEO_OPENGL
103         if (_SDL_GLView) {
104             _SDL_GLView->UnlockGL();
105             RemoveChild(_SDL_GLView);   /* Why was this outside the if
106                                             statement before? */
107         }
108 
109 #endif
110         Unlock();
111 #if SDL_VIDEO_OPENGL
112         if (_SDL_GLView) {
113             delete _SDL_GLView;
114         }
115 #endif
116 
117         /* Clean up framebuffer stuff */
118         _buffer_locker->Lock();
119 #ifdef DRAWTHREAD
120         wait_for_thread(_draw_thread_id, &result);
121 #endif
122         free(_clips);
123         delete _buffer_locker;
124     }
125 
126 
127     /* * * * * OpenGL functionality * * * * */
128 #if SDL_VIDEO_OPENGL
CreateGLView(Uint32 gl_flags)129     virtual BGLView *CreateGLView(Uint32 gl_flags) {
130         Lock();
131         if (_SDL_GLView == NULL) {
132             _SDL_GLView = new BGLView(Bounds(), "SDL GLView",
133                                      B_FOLLOW_ALL_SIDES,
134                                      (B_WILL_DRAW | B_FRAME_EVENTS),
135                                      gl_flags);
136         }
137         AddChild(_SDL_GLView);
138         _SDL_GLView->EnableDirectMode(true);
139         _SDL_GLView->LockGL();  /* "New" GLViews are created */
140         Unlock();
141         return (_SDL_GLView);
142     }
143 
RemoveGLView()144     virtual void RemoveGLView() {
145         Lock();
146         if(_SDL_GLView) {
147             _SDL_GLView->UnlockGL();
148             RemoveChild(_SDL_GLView);
149         }
150         Unlock();
151     }
152 
SwapBuffers(void)153     virtual void SwapBuffers(void) {
154         _SDL_GLView->UnlockGL();
155         _SDL_GLView->LockGL();
156         _SDL_GLView->SwapBuffers();
157     }
158 #endif
159 
160     /* * * * * Framebuffering* * * * */
DirectConnected(direct_buffer_info * info)161     virtual void DirectConnected(direct_buffer_info *info) {
162         if(!_connected && _connection_disabled) {
163             return;
164         }
165 
166         /* Determine if the pixel buffer is usable after this update */
167         _trash_window_buffer =      _trash_window_buffer
168                                 || ((info->buffer_state & B_BUFFER_RESIZED)
169                                 || (info->buffer_state & B_BUFFER_RESET)
170                                 || (info->driver_state == B_MODE_CHANGED));
171         LockBuffer();
172 
173         switch(info->buffer_state & B_DIRECT_MODE_MASK) {
174         case B_DIRECT_START:
175             _connected = true;
176 
177         case B_DIRECT_MODIFY:
178             if(_clips) {
179                 free(_clips);
180                 _clips = NULL;
181             }
182 
183             _num_clips = info->clip_list_count;
184             _clips = (clipping_rect *)malloc(_num_clips*sizeof(clipping_rect));
185             if(_clips) {
186                 memcpy(_clips, info->clip_list,
187                     _num_clips*sizeof(clipping_rect));
188 
189                 _bits = (uint8*) info->bits;
190                 _row_bytes = info->bytes_per_row;
191                 _bounds = info->window_bounds;
192                 _bytes_per_px = info->bits_per_pixel / 8;
193                 _buffer_dirty = true;
194             }
195             break;
196 
197         case B_DIRECT_STOP:
198             _connected = false;
199             break;
200         }
201 #if SDL_VIDEO_OPENGL
202         if(_SDL_GLView) {
203             _SDL_GLView->DirectConnected(info);
204         }
205 #endif
206 
207 
208         /* Call the base object directconnected */
209         BDirectWindow::DirectConnected(info);
210 
211         UnlockBuffer();
212 
213     }
214 
215 
216 
217 
218     /* * * * * Event sending * * * * */
219     /* Hook functions */
FrameMoved(BPoint origin)220     virtual void FrameMoved(BPoint origin) {
221         /* Post a message to the BApp so that it can handle the window event */
222         BMessage msg(BAPP_WINDOW_MOVED);
223         msg.AddInt32("window-x", (int)origin.x);
224         msg.AddInt32("window-y", (int)origin.y);
225         _PostWindowEvent(msg);
226 
227         /* Perform normal hook operations */
228         BDirectWindow::FrameMoved(origin);
229     }
230 
FrameResized(float width,float height)231     virtual void FrameResized(float width, float height) {
232         /* Post a message to the BApp so that it can handle the window event */
233         BMessage msg(BAPP_WINDOW_RESIZED);
234 
235         msg.AddInt32("window-w", (int)width + 1);
236         msg.AddInt32("window-h", (int)height + 1);
237         _PostWindowEvent(msg);
238 
239         /* Perform normal hook operations */
240         BDirectWindow::FrameResized(width, height);
241     }
242 
QuitRequested()243     virtual bool QuitRequested() {
244         BMessage msg(BAPP_WINDOW_CLOSE_REQUESTED);
245         _PostWindowEvent(msg);
246 
247         /* We won't allow a quit unless asked by DestroyWindow() */
248         return false;
249     }
250 
WindowActivated(bool active)251     virtual void WindowActivated(bool active) {
252         BMessage msg(BAPP_KEYBOARD_FOCUS);  /* Mouse focus sold separately */
253         _PostWindowEvent(msg);
254     }
255 
Zoom(BPoint origin,float width,float height)256     virtual void Zoom(BPoint origin,
257                 float width,
258                 float height) {
259         BMessage msg(BAPP_MAXIMIZE);    /* Closest thing to maximization Haiku has */
260         _PostWindowEvent(msg);
261 
262         /* Before the window zooms, record its size */
263         if( !_prev_frame )
264             _prev_frame = new BRect(Frame());
265 
266         /* Perform normal hook operations */
267         BDirectWindow::Zoom(origin, width, height);
268     }
269 
270     /* Member functions */
Show()271     virtual void Show() {
272         while(IsHidden()) {
273             BDirectWindow::Show();
274         }
275         _shown = true;
276 
277         BMessage msg(BAPP_SHOW);
278         _PostWindowEvent(msg);
279     }
280 
Hide()281     virtual void Hide() {
282         BDirectWindow::Hide();
283         _shown = false;
284 
285         BMessage msg(BAPP_HIDE);
286         _PostWindowEvent(msg);
287     }
288 
Minimize(bool minimize)289     virtual void Minimize(bool minimize) {
290         BDirectWindow::Minimize(minimize);
291         int32 minState = (minimize ? BAPP_MINIMIZE : BAPP_RESTORE);
292 
293         BMessage msg(minState);
294         _PostWindowEvent(msg);
295     }
296 
297 
298     /* BView message interruption */
DispatchMessage(BMessage * msg,BHandler * target)299     virtual void DispatchMessage(BMessage * msg, BHandler * target)
300     {
301         BPoint where;   /* Used by mouse moved */
302         int32 buttons;  /* Used for mouse button events */
303         int32 key;      /* Used for key events */
304 
305         switch (msg->what) {
306         case B_MOUSE_MOVED:
307             int32 transit;
308             if (msg->FindPoint("where", &where) == B_OK
309                 && msg->FindInt32("be:transit", &transit) == B_OK) {
310                 _MouseMotionEvent(where, transit);
311             }
312 
313             /* FIXME: Apparently a button press/release event might be dropped
314                if made before before a different button is released.  Does
315                B_MOUSE_MOVED have the data needed to check if a mouse button
316                state has changed? */
317             if (msg->FindInt32("buttons", &buttons) == B_OK) {
318                 _MouseButtonEvent(buttons);
319             }
320             break;
321 
322         case B_MOUSE_DOWN:
323         case B_MOUSE_UP:
324             /* _MouseButtonEvent() detects any and all buttons that may have
325                changed state, as well as that button's new state */
326             if (msg->FindInt32("buttons", &buttons) == B_OK) {
327                 _MouseButtonEvent(buttons);
328             }
329             break;
330 
331         case B_MOUSE_WHEEL_CHANGED:
332             float x, y;
333             if (msg->FindFloat("be:wheel_delta_x", &x) == B_OK
334                 && msg->FindFloat("be:wheel_delta_y", &y) == B_OK) {
335                     _MouseWheelEvent((int)x, (int)y);
336             }
337             break;
338 
339         case B_KEY_DOWN:
340             {
341                 int32 i = 0;
342                 int8 byte;
343                 int8 bytes[4] = { 0, 0, 0, 0 };
344                 while (i < 4 && msg->FindInt8("byte", i, &byte) == B_OK) {
345                     bytes[i] = byte;
346                     i++;
347                 }
348                 if (msg->FindInt32("key", &key) == B_OK) {
349                     _KeyEvent((SDL_Scancode)key, &bytes[0], i, SDL_PRESSED);
350                 }
351             }
352             break;
353 
354         case B_UNMAPPED_KEY_DOWN:      /* modifier keys are unmapped */
355             if (msg->FindInt32("key", &key) == B_OK) {
356                 _KeyEvent((SDL_Scancode)key, NULL, 0, SDL_PRESSED);
357             }
358             break;
359 
360         case B_KEY_UP:
361         case B_UNMAPPED_KEY_UP:        /* modifier keys are unmapped */
362             if (msg->FindInt32("key", &key) == B_OK) {
363                 _KeyEvent(key, NULL, 0, SDL_RELEASED);
364             }
365             break;
366 
367         default:
368             /* move it after switch{} so it's always handled
369                that way we keep Haiku features like:
370                - CTRL+Q to close window (and other shortcuts)
371                - PrintScreen to make screenshot into /boot/home
372                - etc.. */
373             /* BDirectWindow::DispatchMessage(msg, target); */
374             break;
375         }
376 
377         BDirectWindow::DispatchMessage(msg, target);
378     }
379 
380     /* Handle command messages */
MessageReceived(BMessage * message)381     virtual void MessageReceived(BMessage* message) {
382         switch (message->what) {
383             /* Handle commands from SDL */
384             case BWIN_SET_TITLE:
385                 _SetTitle(message);
386                 break;
387             case BWIN_MOVE_WINDOW:
388                 _MoveTo(message);
389                 break;
390             case BWIN_RESIZE_WINDOW:
391                 _ResizeTo(message);
392                 break;
393             case BWIN_SET_BORDERED:
394                 _SetBordered(message);
395                 break;
396             case BWIN_SET_RESIZABLE:
397                 _SetResizable(message);
398                 break;
399             case BWIN_SHOW_WINDOW:
400                 Show();
401                 break;
402             case BWIN_HIDE_WINDOW:
403                 Hide();
404                 break;
405             case BWIN_MAXIMIZE_WINDOW:
406                 BWindow::Zoom();
407                 break;
408             case BWIN_MINIMIZE_WINDOW:
409                 Minimize(true);
410                 break;
411             case BWIN_RESTORE_WINDOW:
412                 _Restore();
413                 break;
414             case BWIN_FULLSCREEN:
415                 _SetFullScreen(message);
416                 break;
417             default:
418                 /* Perform normal message handling */
419                 BDirectWindow::MessageReceived(message);
420                 break;
421         }
422 
423     }
424 
425 
426 
427     /* Accessor methods */
IsShown()428     bool IsShown() { return _shown; }
GetID()429     int32 GetID() { return _id; }
GetRowBytes()430     uint32 GetRowBytes() { return _row_bytes; }
GetFbX()431     int32 GetFbX() { return _bounds.left; }
GetFbY()432     int32 GetFbY() { return _bounds.top; }
ConnectionEnabled()433     bool ConnectionEnabled() { return !_connection_disabled; }
Connected()434     bool Connected() { return _connected; }
GetClips()435     clipping_rect *GetClips() { return _clips; }
GetNumClips()436     int32 GetNumClips() { return _num_clips; }
GetBufferPx()437     uint8* GetBufferPx() { return _bits; }
GetBytesPerPx()438     int32 GetBytesPerPx() { return _bytes_per_px; }
CanTrashWindowBuffer()439     bool CanTrashWindowBuffer() { return _trash_window_buffer; }
BufferExists()440     bool BufferExists() { return _buffer_created; }
BufferIsDirty()441     bool BufferIsDirty() { return _buffer_dirty; }
GetBitmap()442     BBitmap *GetBitmap() { return _bitmap; }
443 #if SDL_VIDEO_OPENGL
GetGLView()444     BGLView *GetGLView() { return _SDL_GLView; }
445 #endif
446 
447     /* Setter methods */
SetID(int32 id)448     void SetID(int32 id) { _id = id; }
SetBufferExists(bool bufferExists)449     void SetBufferExists(bool bufferExists) { _buffer_created = bufferExists; }
LockBuffer()450     void LockBuffer() { _buffer_locker->Lock(); }
UnlockBuffer()451     void UnlockBuffer() { _buffer_locker->Unlock(); }
SetBufferDirty(bool bufferDirty)452     void SetBufferDirty(bool bufferDirty) { _buffer_dirty = bufferDirty; }
SetTrashBuffer(bool trash)453     void SetTrashBuffer(bool trash) { _trash_window_buffer = trash;     }
SetBitmap(BBitmap * bitmap)454     void SetBitmap(BBitmap *bitmap) { _bitmap = bitmap; }
455 
456 
457 private:
458     /* Event redirection */
_MouseMotionEvent(BPoint & where,int32 transit)459     void _MouseMotionEvent(BPoint &where, int32 transit) {
460         if(transit == B_EXITED_VIEW) {
461             /* Change mouse focus */
462             if(_mouse_focused) {
463                 _MouseFocusEvent(false);
464             }
465         } else {
466             /* Change mouse focus */
467             if (!_mouse_focused) {
468                 _MouseFocusEvent(true);
469             }
470             BMessage msg(BAPP_MOUSE_MOVED);
471             msg.AddInt32("x", (int)where.x);
472             msg.AddInt32("y", (int)where.y);
473 
474             _PostWindowEvent(msg);
475         }
476     }
477 
_MouseFocusEvent(bool focusGained)478     void _MouseFocusEvent(bool focusGained) {
479         _mouse_focused = focusGained;
480         BMessage msg(BAPP_MOUSE_FOCUS);
481         msg.AddBool("focusGained", focusGained);
482         _PostWindowEvent(msg);
483 
484 /* FIXME: Why were these here?
485  if false: be_app->SetCursor(B_HAND_CURSOR);
486  if true:  SDL_SetCursor(NULL); */
487     }
488 
_MouseButtonEvent(int32 buttons)489     void _MouseButtonEvent(int32 buttons) {
490         int32 buttonStateChange = buttons ^ _last_buttons;
491 
492         /* Make sure at least one button has changed state */
493         if( !(buttonStateChange) ) {
494             return;
495         }
496 
497         /* Add any mouse button events */
498         if(buttonStateChange & B_PRIMARY_MOUSE_BUTTON) {
499             _SendMouseButton(SDL_BUTTON_LEFT, buttons &
500                 B_PRIMARY_MOUSE_BUTTON);
501         }
502         if(buttonStateChange & B_SECONDARY_MOUSE_BUTTON) {
503             _SendMouseButton(SDL_BUTTON_RIGHT, buttons &
504                 B_PRIMARY_MOUSE_BUTTON);
505         }
506         if(buttonStateChange & B_TERTIARY_MOUSE_BUTTON) {
507             _SendMouseButton(SDL_BUTTON_MIDDLE, buttons &
508                 B_PRIMARY_MOUSE_BUTTON);
509         }
510 
511         _last_buttons = buttons;
512     }
513 
_SendMouseButton(int32 button,int32 state)514     void _SendMouseButton(int32 button, int32 state) {
515         BMessage msg(BAPP_MOUSE_BUTTON);
516         msg.AddInt32("button-id", button);
517         msg.AddInt32("button-state", state);
518         _PostWindowEvent(msg);
519     }
520 
_MouseWheelEvent(int32 x,int32 y)521     void _MouseWheelEvent(int32 x, int32 y) {
522         /* Create a message to pass along to the BeApp thread */
523         BMessage msg(BAPP_MOUSE_WHEEL);
524         msg.AddInt32("xticks", x);
525         msg.AddInt32("yticks", y);
526         _PostWindowEvent(msg);
527     }
528 
_KeyEvent(int32 keyCode,const int8 * keyUtf8,const ssize_t & len,int32 keyState)529     void _KeyEvent(int32 keyCode, const int8 *keyUtf8, const ssize_t & len, int32 keyState) {
530         /* Create a message to pass along to the BeApp thread */
531         BMessage msg(BAPP_KEY);
532         msg.AddInt32("key-state", keyState);
533         msg.AddInt32("key-scancode", keyCode);
534         if (keyUtf8 != NULL) {
535         	msg.AddData("key-utf8", B_INT8_TYPE, (const void*)keyUtf8, len);
536         }
537         be_app->PostMessage(&msg);
538     }
539 
_RepaintEvent()540     void _RepaintEvent() {
541         /* Force a repaint: Call the SDL exposed event */
542         BMessage msg(BAPP_REPAINT);
543         _PostWindowEvent(msg);
544     }
_PostWindowEvent(BMessage & msg)545     void _PostWindowEvent(BMessage &msg) {
546         msg.AddInt32("window-id", _id);
547         be_app->PostMessage(&msg);
548     }
549 
550     /* Command methods (functions called upon by SDL) */
_SetTitle(BMessage * msg)551     void _SetTitle(BMessage *msg) {
552         const char *title;
553         if(
554             msg->FindString("window-title", &title) != B_OK
555         ) {
556             return;
557         }
558         SetTitle(title);
559     }
560 
_MoveTo(BMessage * msg)561     void _MoveTo(BMessage *msg) {
562         int32 x, y;
563         if(
564             msg->FindInt32("window-x", &x) != B_OK ||
565             msg->FindInt32("window-y", &y) != B_OK
566         ) {
567             return;
568         }
569         MoveTo(x, y);
570     }
571 
_ResizeTo(BMessage * msg)572     void _ResizeTo(BMessage *msg) {
573         int32 w, h;
574         if(
575             msg->FindInt32("window-w", &w) != B_OK ||
576             msg->FindInt32("window-h", &h) != B_OK
577         ) {
578             return;
579         }
580         ResizeTo(w, h);
581     }
582 
_SetBordered(BMessage * msg)583     void _SetBordered(BMessage *msg) {
584         bool bEnabled;
585         if(msg->FindBool("window-border", &bEnabled) != B_OK) {
586             return;
587         }
588         SetLook(bEnabled ? B_BORDERED_WINDOW_LOOK : B_NO_BORDER_WINDOW_LOOK);
589     }
590 
_SetResizable(BMessage * msg)591     void _SetResizable(BMessage *msg) {
592         bool bEnabled;
593         if(msg->FindBool("window-resizable", &bEnabled) != B_OK) {
594             return;
595         }
596         if (bEnabled) {
597             SetFlags(Flags() & ~(B_NOT_RESIZABLE | B_NOT_ZOOMABLE));
598         } else {
599             SetFlags(Flags() | (B_NOT_RESIZABLE | B_NOT_ZOOMABLE));
600         }
601     }
602 
_Restore()603     void _Restore() {
604         if(IsMinimized()) {
605             Minimize(false);
606         } else if(IsHidden()) {
607             Show();
608         } else if(_prev_frame != NULL) {    /* Zoomed */
609             MoveTo(_prev_frame->left, _prev_frame->top);
610             ResizeTo(_prev_frame->Width(), _prev_frame->Height());
611         }
612     }
613 
_SetFullScreen(BMessage * msg)614     void _SetFullScreen(BMessage *msg) {
615         bool fullscreen;
616         if(
617             msg->FindBool("fullscreen", &fullscreen) != B_OK
618         ) {
619             return;
620         }
621         SetFullScreen(fullscreen);
622     }
623 
624     /* Members */
625 #if SDL_VIDEO_OPENGL
626     BGLView * _SDL_GLView;
627 #endif
628 
629     int32 _last_buttons;
630     int32 _id;  /* Window id used by SDL_BApp */
631     bool  _mouse_focused;       /* Does this window have mouse focus? */
632     bool  _shown;
633     bool  _inhibit_resize;
634 
635     BRect *_prev_frame; /* Previous position and size of the window */
636 
637     /* Framebuffer members */
638     bool            _connected,
639                     _connection_disabled,
640                     _buffer_created,
641                     _buffer_dirty,
642                     _trash_window_buffer;
643     uint8          *_bits;
644     uint32          _row_bytes;
645     clipping_rect   _bounds;
646     BLocker        *_buffer_locker;
647     clipping_rect  *_clips;
648     int32           _num_clips;
649     int32           _bytes_per_px;
650     thread_id       _draw_thread_id;
651 
652     BBitmap        *_bitmap;
653 };
654 
655 
656 /* FIXME:
657  * An explanation of framebuffer flags.
658  *
659  * _connected -           Original variable used to let the drawing thread know
660  *                         when changes are being made to the other framebuffer
661  *                         members.
662  * _connection_disabled - Used to signal to the drawing thread that the window
663  *                         is closing, and the thread should exit.
664  * _buffer_created -      True if the current buffer is valid
665  * _buffer_dirty -        True if the window should be redrawn.
666  * _trash_window_buffer - True if the window buffer needs to be trashed partway
667  *                         through a draw cycle.  Occurs when the previous
668  *                         buffer provided by DirectConnected() is invalidated.
669  */
670 #endif
671