source: libcaca/trunk/caca/driver/win32.c @ 4852

Last change on this file since 4852 was 4852, checked in by Sam Hocevar, 8 years ago

win32: improvements to the Win32 driver by Bastian Märkisch <bmaerkisch@web.de>:

  • Save and correctly restore console input mode, ie. do not change settings of the original console.
  • Fix hiding of cursor.
  • Free console again if the driver actually created a new one.
  • Default canvas size is current window size.
  • Base calculation on current font.
  • Properly handle press and release events of up to 5 mouse buttons.
  • Map right mouse button to button #3. This is a change in behaviour, which is desirable for cross-platform codes.
  • Generate two click events for double mouse clicks.
  • Mouse wheel support, creates button press events. Button numbers are identical to X11 mappings.
  • Handle window buffer resize events.
  • Property svn:keywords set to Id
File size: 17.5 KB
Line 
1/*
2 *  libcaca       Colour ASCII-Art library
3 *  Copyright (c) 2002-2012 Sam Hocevar <sam@hocevar.net>
4 *                2012 Bastian Märkisch <bmaerkisch@web.de>
5 *                All Rights Reserved
6 *
7 *  This library is free software. It comes without any warranty, to
8 *  the extent permitted by applicable law. You can redistribute it
9 *  and/or modify it under the terms of the Do What The Fuck You Want
10 *  To Public License, Version 2, as published by Sam Hocevar. See
11 *  http://sam.zoy.org/wtfpl/COPYING for more details.
12 */
13
14/*
15 *  This file contains the libcaca Win32 input and output driver
16 */
17
18#include "config.h"
19
20#if defined(USE_WIN32)
21
22#define _WIN32_WINNT 0x500 /* Require WinXP or later */
23#define WIN32_LEAN_AND_MEAN
24#include <windows.h>
25
26#ifdef __MINGW32__
27/* This is missing from the MinGW headers. */
28#   if (_WIN32_WINNT >= 0x0500)
29BOOL WINAPI GetCurrentConsoleFont(HANDLE hConsoleOutput, BOOL bMaximumWindow,
30                                  PCONSOLE_FONT_INFO lpConsoleCurrentFont);
31#   endif
32#endif
33#ifndef MOUSE_HWHEELED
34#   define MOUSE_HWHEELED 0x0008
35#endif
36
37#include <stdlib.h>
38#include <stdio.h>
39
40#include "caca.h"
41#include "caca_internals.h"
42
43/*
44 * Global variables
45 */
46
47static int const win32_fg_palette[] =
48{
49    0,
50    FOREGROUND_BLUE,
51    FOREGROUND_GREEN,
52    FOREGROUND_GREEN | FOREGROUND_BLUE,
53    FOREGROUND_RED,
54    FOREGROUND_RED | FOREGROUND_BLUE,
55    FOREGROUND_RED | FOREGROUND_GREEN,
56    FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE,
57    FOREGROUND_INTENSITY,
58    FOREGROUND_INTENSITY | FOREGROUND_BLUE,
59    FOREGROUND_INTENSITY | FOREGROUND_GREEN,
60    FOREGROUND_INTENSITY | FOREGROUND_GREEN | FOREGROUND_BLUE,
61    FOREGROUND_INTENSITY | FOREGROUND_RED,
62    FOREGROUND_INTENSITY | FOREGROUND_RED | FOREGROUND_BLUE,
63    FOREGROUND_INTENSITY | FOREGROUND_RED | FOREGROUND_GREEN,
64    FOREGROUND_INTENSITY | FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE
65};
66
67static int const win32_bg_palette[] =
68{
69    0,
70    BACKGROUND_BLUE,
71    BACKGROUND_GREEN,
72    BACKGROUND_GREEN | BACKGROUND_BLUE,
73    BACKGROUND_RED,
74    BACKGROUND_RED | BACKGROUND_BLUE,
75    BACKGROUND_RED | BACKGROUND_GREEN,
76    BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE,
77    BACKGROUND_INTENSITY,
78    BACKGROUND_INTENSITY | BACKGROUND_BLUE,
79    BACKGROUND_INTENSITY | BACKGROUND_GREEN,
80    BACKGROUND_INTENSITY | BACKGROUND_GREEN | BACKGROUND_BLUE,
81    BACKGROUND_INTENSITY | BACKGROUND_RED,
82    BACKGROUND_INTENSITY | BACKGROUND_RED | BACKGROUND_BLUE,
83    BACKGROUND_INTENSITY | BACKGROUND_RED | BACKGROUND_GREEN,
84    BACKGROUND_INTENSITY | BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE
85};
86
87struct driver_private
88{
89    HANDLE hin, hout, screen;
90    CHAR_INFO *buffer;
91    CONSOLE_CURSOR_INFO cci;
92    DWORD mouse_state;
93    DWORD mode;
94    BOOL new_console;
95};
96
97static int win32_init_graphics(caca_display_t *dp)
98{
99    int width = caca_get_canvas_width(dp->cv);
100    int height = caca_get_canvas_height(dp->cv);
101    CONSOLE_SCREEN_BUFFER_INFO csbi;
102    CONSOLE_CURSOR_INFO cci_screen;
103    SMALL_RECT rect;
104    COORD size;
105
106    dp->drv.p = malloc(sizeof(struct driver_private));
107
108    /* This call is allowed to fail in case we already have a console. */
109    dp->drv.p->new_console = AllocConsole();
110
111    dp->drv.p->hin = GetStdHandle(STD_INPUT_HANDLE);
112    dp->drv.p->hout = CreateFile("CONOUT$", GENERIC_READ | GENERIC_WRITE,
113                                 FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,
114                                 OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
115    if(dp->drv.p->hout == INVALID_HANDLE_VALUE)
116        return -1;
117
118    /* Preserve state information */
119    GetConsoleCursorInfo(dp->drv.p->hout, &dp->drv.p->cci);
120
121    dp->drv.p->screen =
122        CreateConsoleScreenBuffer(GENERIC_READ | GENERIC_WRITE,
123                                  0, NULL, CONSOLE_TEXTMODE_BUFFER, NULL);
124    if(!dp->drv.p->screen || dp->drv.p->screen == INVALID_HANDLE_VALUE)
125        return -1;
126    dp->drv.p->mouse_state = 0;
127
128    /* Set the new console size, default to current screen size. */
129    size.X = width ? width : 80;
130    size.Y = height ? height : 25;
131    if ((width <= 0) && (height <= 0))
132    {
133        CONSOLE_SCREEN_BUFFER_INFO info;
134
135        if (GetConsoleScreenBufferInfo(dp->drv.p->hout, &info) != 0)
136            size = info.dwSize;
137    }
138    SetConsoleScreenBufferSize(dp->drv.p->screen, size);
139
140    rect.Left = rect.Top = 0;
141    rect.Right = size.X - 1;
142    rect.Bottom = size.Y - 1;
143    SetConsoleWindowInfo(dp->drv.p->screen, TRUE, &rect);
144
145    /* Report our new size to libcaca */
146    if(!GetConsoleScreenBufferInfo(dp->drv.p->screen, &csbi))
147        return -1;
148
149    dp->resize.allow = 1;
150    caca_set_canvas_size(dp->cv,
151                          csbi.srWindow.Right - csbi.srWindow.Left + 1,
152                          csbi.srWindow.Bottom - csbi.srWindow.Top + 1);
153    width = caca_get_canvas_width(dp->cv);
154    height = caca_get_canvas_height(dp->cv);
155    dp->resize.allow = 0;
156
157    SetConsoleMode(dp->drv.p->screen, 0);
158
159    /* We want mouse and window resize events. */
160    GetConsoleMode(dp->drv.p->hin, &dp->drv.p->mode);
161    SetConsoleMode(dp->drv.p->hin, ENABLE_MOUSE_INPUT | ENABLE_WINDOW_INPUT);
162
163    cci_screen.dwSize = 1;  /* must be > 0, see MSDN */
164    cci_screen.bVisible = FALSE;
165    SetConsoleCursorInfo(dp->drv.p->screen, &cci_screen);
166
167    SetConsoleActiveScreenBuffer(dp->drv.p->screen);
168
169    dp->drv.p->buffer = malloc(width * height
170                               * sizeof(CHAR_INFO));
171    if(dp->drv.p->buffer == NULL)
172        return -1;
173
174    return 0;
175}
176
177static int win32_end_graphics(caca_display_t *dp)
178{
179    SetConsoleActiveScreenBuffer(dp->drv.p->hout);
180    CloseHandle(dp->drv.p->screen);
181
182    /* Reset console parameters */
183    SetConsoleMode(dp->drv.p->hin, dp->drv.p->mode);
184    SetConsoleCursorInfo(dp->drv.p->hout, &dp->drv.p->cci);
185    CloseHandle(dp->drv.p->hout);
186
187    /* We only free the console if we successfully created a new one before */
188    if (dp->drv.p->new_console)
189        FreeConsole();
190
191    free(dp->drv.p);
192
193    return 0;
194}
195
196static int win32_set_display_title(caca_display_t *dp, char const *title)
197{
198    SetConsoleTitle(title);
199    return 0;
200}
201
202static int win32_get_display_width(caca_display_t const *dp)
203{
204    /* Fallback to a 6x10 font */
205    int font_width = 6;
206
207#if (_WIN32_WINNT >= 0x500)
208    CONSOLE_FONT_INFO info;
209    if (GetCurrentConsoleFont(dp->drv.p->screen, FALSE, &info) != 0)
210        font_width = info.dwFontSize.X;
211#endif
212    return font_width * caca_get_canvas_width(dp->cv);
213}
214
215static int win32_get_display_height(caca_display_t const *dp)
216{
217    /* Fallback to a 6x10 font */
218    int font_height = 10;
219
220#if (_WIN32_WINNT >= 0x500)
221    CONSOLE_FONT_INFO info;
222    if (GetCurrentConsoleFont(dp->drv.p->screen, FALSE, &info) != 0)
223        font_height = info.dwFontSize.Y;
224#endif
225    return font_height * caca_get_canvas_height(dp->cv);
226}
227
228static void win32_display(caca_display_t *dp)
229{
230    COORD size, pos;
231    SMALL_RECT rect;
232    CHAR_INFO *buffer = dp->drv.p->buffer;
233    uint32_t const *cvchars = caca_get_canvas_chars(dp->cv);
234    uint32_t const *cvattrs = caca_get_canvas_attrs(dp->cv);
235    int width = caca_get_canvas_width(dp->cv);
236    int height = caca_get_canvas_height(dp->cv);
237    int n;
238
239    /* Render everything to our screen buffer */
240    for(n = height * width; n--; )
241    {
242        uint32_t ch = *cvchars++;
243        uint16_t bgfg = caca_attr_to_ansi(*cvattrs);
244        uint8_t fg = bgfg & 0xf;
245        uint8_t bg = bgfg >> 4;
246
247#if 0
248        if(ch > 0x00000020 && ch < 0x00000080)
249            dp->drv.p->buffer[i].Char.AsciiChar = (uint8_t)ch;
250        else
251            dp->drv.p->buffer[i].Char.AsciiChar = ' ';
252#else
253        if(n && *cvchars == CACA_MAGIC_FULLWIDTH)
254            ;
255        else if(ch > 0x00000020 && ch < 0x00010000)
256            buffer->Char.UnicodeChar = (uint16_t)ch;
257        else
258            buffer->Char.UnicodeChar = (uint16_t)' ';
259#endif
260
261        buffer->Attributes = win32_fg_palette[fg < 0x10 ? fg : CACA_LIGHTGRAY]
262                              | win32_bg_palette[bg < 0x10 ? bg : CACA_BLACK];
263        cvattrs++;
264        buffer++;
265    }
266
267    /* Blit the screen buffer */
268    size.X = width;
269    size.Y = height;
270    pos.X = pos.Y = 0;
271    rect.Left = rect.Top = 0;
272    rect.Right = width - 1;
273    rect.Bottom = height - 1;
274#if 0
275    WriteConsoleOutput(dp->drv.p->screen, dp->drv.p->buffer, size, pos, &rect);
276#else
277    /* FIXME: would this benefit from dirty rectangles? */
278    WriteConsoleOutputW(dp->drv.p->screen, dp->drv.p->buffer, size, pos, &rect);
279#endif
280}
281
282static void win32_handle_resize(caca_display_t *dp)
283{
284    /* We only need to resize the internal buffer. */
285    dp->drv.p->buffer = realloc(dp->drv.p->buffer,
286                        dp->resize.w * dp->resize.h * sizeof(CHAR_INFO));
287}
288
289static int win32_get_event(caca_display_t *dp, caca_privevent_t *ev)
290{
291    INPUT_RECORD rec;
292    DWORD num;
293
294    for( ; ; )
295    {
296        HANDLE hin = GetStdHandle(STD_INPUT_HANDLE);
297        if(hin == INVALID_HANDLE_VALUE)
298            break;
299
300        GetNumberOfConsoleInputEvents(hin, &num);
301        if(num == 0)
302            break;
303
304        if(!ReadConsoleInput(hin, &rec, 1, &num) || (num == 0))
305            break;
306
307        if(rec.EventType == KEY_EVENT)
308        {
309            if(rec.Event.KeyEvent.bKeyDown)
310                ev->type = CACA_EVENT_KEY_PRESS;
311            else
312                ev->type = CACA_EVENT_KEY_RELEASE;
313
314            if(rec.Event.KeyEvent.uChar.AsciiChar)
315            {
316                ev->data.key.ch = rec.Event.KeyEvent.uChar.AsciiChar;
317                ev->data.key.utf32 = (uint32_t)ev->data.key.ch;
318                ev->data.key.utf8[0] = ev->data.key.ch;
319                ev->data.key.utf8[1] = '\0';
320
321                return 1;
322            }
323            else
324            {
325                switch (rec.Event.KeyEvent.wVirtualKeyCode)
326                {
327                case VK_TAB:        ev->data.key.ch = '\t';              break;
328                case VK_RETURN:     ev->data.key.ch = '\r';              break;
329                case VK_ESCAPE:     ev->data.key.ch = '\033';            break;
330                case VK_SPACE:      ev->data.key.ch = ' ';               break;
331                case VK_DELETE:     ev->data.key.ch = '\x7f';            break;
332
333                case VK_LEFT:       ev->data.key.ch = CACA_KEY_LEFT;     break;
334                case VK_RIGHT:      ev->data.key.ch = CACA_KEY_RIGHT;    break;
335                case VK_UP:         ev->data.key.ch = CACA_KEY_UP;       break;
336                case VK_DOWN:       ev->data.key.ch = CACA_KEY_DOWN;     break;
337
338                case VK_INSERT:     ev->data.key.ch = CACA_KEY_INSERT;   break;
339                case VK_HOME:       ev->data.key.ch = CACA_KEY_HOME;     break;
340                case VK_END:        ev->data.key.ch = CACA_KEY_END;      break;
341                case VK_PRIOR:      ev->data.key.ch = CACA_KEY_PAGEUP;   break;
342                case VK_NEXT:       ev->data.key.ch = CACA_KEY_PAGEDOWN; break;
343
344                case VK_F1:         ev->data.key.ch = CACA_KEY_F1;       break;
345                case VK_F2:         ev->data.key.ch = CACA_KEY_F2;       break;
346                case VK_F3:         ev->data.key.ch = CACA_KEY_F3;       break;
347                case VK_F4:         ev->data.key.ch = CACA_KEY_F4;       break;
348                case VK_F5:         ev->data.key.ch = CACA_KEY_F5;       break;
349                case VK_F6:         ev->data.key.ch = CACA_KEY_F6;       break;
350                case VK_F7:         ev->data.key.ch = CACA_KEY_F7;       break;
351                case VK_F8:         ev->data.key.ch = CACA_KEY_F8;       break;
352                case VK_F9:         ev->data.key.ch = CACA_KEY_F9;       break;
353                case VK_F10:        ev->data.key.ch = CACA_KEY_F10;      break;
354                case VK_F11:        ev->data.key.ch = CACA_KEY_F11;      break;
355                case VK_F12:        ev->data.key.ch = CACA_KEY_F12;      break;
356                case VK_F13:        ev->data.key.ch = CACA_KEY_F13;      break;
357                case VK_F14:        ev->data.key.ch = CACA_KEY_F14;      break;
358                case VK_F15:        ev->data.key.ch = CACA_KEY_F15;      break;
359
360                case VK_NUMPAD0:    ev->data.key.ch = '0';               break;
361                case VK_NUMPAD1:    ev->data.key.ch = '1';               break;
362                case VK_NUMPAD2:    ev->data.key.ch = '2';               break;
363                case VK_NUMPAD3:    ev->data.key.ch = '3';               break;
364                case VK_NUMPAD4:    ev->data.key.ch = '4';               break;
365                case VK_NUMPAD5:    ev->data.key.ch = '5';               break;
366                case VK_NUMPAD6:    ev->data.key.ch = '6';               break;
367                case VK_NUMPAD7:    ev->data.key.ch = '7';               break;
368                case VK_NUMPAD8:    ev->data.key.ch = '8';               break;
369                case VK_NUMPAD9:    ev->data.key.ch = '9';               break;
370                case VK_MULTIPLY:   ev->data.key.ch = '*';               break;
371                case VK_ADD:        ev->data.key.ch = '+';               break;
372                case VK_SEPARATOR:  ev->data.key.ch = ',';               break;
373                case VK_SUBTRACT:   ev->data.key.ch = '-';               break;
374                case VK_DECIMAL:    ev->data.key.ch = '.';               break;
375                case VK_DIVIDE:     ev->data.key.ch = '/';               break;
376
377                default: ev->type = CACA_EVENT_NONE; return 0;
378                }
379
380                if((ev->data.key.ch > 0)
381                    &&
382                    (ev->data.key.ch <= 0x7f))
383                {
384                    ev->data.key.utf32 = (uint32_t)ev->data.key.ch;
385                    ev->data.key.utf8[0] = ev->data.key.ch;
386                    ev->data.key.utf8[1] = '\0';
387                }
388                else
389                {
390                    ev->data.key.utf32 = 0;
391                    ev->data.key.utf8[0] = '\0';
392                }
393
394                return 1;
395            }
396        }
397        else if(rec.EventType == MOUSE_EVENT)
398        {
399            if((rec.Event.MouseEvent.dwEventFlags == 0) ||
400               (rec.Event.MouseEvent.dwEventFlags == DOUBLE_CLICK))
401            {
402                DWORD mouse_state_changed =
403                    dp->drv.p->mouse_state ^ rec.Event.MouseEvent.dwButtonState;
404                int button;
405                DWORD mask = 0x01;
406                int const mapping[] = {1, 3, 2, 8, 9};
407
408                for (button = 1; button <= 5; button++)
409                {
410                    if(mouse_state_changed == mask)
411                    {
412                        if(rec.Event.MouseEvent.dwButtonState & mask)
413                            ev->type = CACA_EVENT_MOUSE_PRESS;
414                        else
415                            ev->type = CACA_EVENT_MOUSE_RELEASE;
416                        ev->data.mouse.button = mapping[button - 1];
417                        dp->drv.p->mouse_state = rec.Event.MouseEvent.dwButtonState;
418                        return 1;
419                    }
420                    mask <<= 1;
421                }
422            }
423            else if(rec.Event.MouseEvent.dwEventFlags == MOUSE_MOVED)
424            {
425                COORD pos = rec.Event.MouseEvent.dwMousePosition;
426
427                if(dp->mouse.x == pos.X && dp->mouse.y == pos.Y)
428                    continue;
429
430                dp->mouse.x = pos.X;
431                dp->mouse.y = pos.Y;
432
433                ev->type = CACA_EVENT_MOUSE_MOTION;
434                ev->data.mouse.x = dp->mouse.x;
435                ev->data.mouse.y = dp->mouse.y;
436                return 1;
437            }
438            else if(rec.Event.MouseEvent.dwEventFlags == MOUSE_WHEELED)
439            {
440                ev->type = CACA_EVENT_MOUSE_PRESS;
441                if((int16_t)HIWORD(rec.Event.MouseEvent.dwButtonState) > 0)
442                    ev->data.mouse.button = 4; /* up */
443                else
444                    ev->data.mouse.button = 5; /* down */
445                return 1;
446            }
447            else if(rec.Event.MouseEvent.dwEventFlags == MOUSE_HWHEELED)
448            {
449                ev->type = CACA_EVENT_MOUSE_PRESS;
450                if((int16_t)HIWORD(rec.Event.MouseEvent.dwButtonState) > 0)
451                    ev->data.mouse.button = 7; /* right */
452                else
453                    ev->data.mouse.button = 6; /* left */
454                return 1;
455            }
456        }
457        else if(rec.EventType == WINDOW_BUFFER_SIZE_EVENT)
458        {
459            int width = caca_get_canvas_width(dp->cv);
460            int height = caca_get_canvas_height(dp->cv);
461            int w = rec.Event.WindowBufferSizeEvent.dwSize.X;
462            int h = rec.Event.WindowBufferSizeEvent.dwSize.Y;
463            if((w == 0) || (h == 0) || ((w == width) && (h == height)))
464                continue;
465
466            /* resize the canvas accordingly */
467            ev->type = CACA_EVENT_RESIZE;
468            dp->resize.w = w;
469            dp->resize.h = h;
470            dp->resize.resized = 1;
471            return 1;
472        }
473
474        /* Unknown event */
475        ev->type = CACA_EVENT_NONE;
476        return 0;
477    }
478
479    /* No event */
480    ev->type = CACA_EVENT_NONE;
481    return 0;
482}
483
484/*
485 * Driver initialisation
486 */
487
488int win32_install(caca_display_t *dp)
489{
490    dp->drv.id = CACA_DRIVER_WIN32;
491    dp->drv.driver = "win32";
492
493    dp->drv.init_graphics = win32_init_graphics;
494    dp->drv.end_graphics = win32_end_graphics;
495    dp->drv.set_display_title = win32_set_display_title;
496    dp->drv.get_display_width = win32_get_display_width;
497    dp->drv.get_display_height = win32_get_display_height;
498    dp->drv.display = win32_display;
499    dp->drv.handle_resize = win32_handle_resize;
500    dp->drv.get_event = win32_get_event;
501    dp->drv.set_mouse = NULL;
502    dp->drv.set_cursor = NULL;
503
504    return 0;
505}
506
507#endif /* USE_WIN32 */
508
Note: See TracBrowser for help on using the repository browser.