| /*++ |
| |
| Copyright (c) 2004 - 2009, Intel Corporation. All rights reserved.<BR> |
| Portions copyright (c) 2008 - 2009, Apple Inc. All rights reserved.<BR> |
| This program and the accompanying materials |
| are licensed and made available under the terms and conditions of the BSD License |
| which accompanies this distribution. The full text of the license may be found at |
| http://opensource.org/licenses/bsd-license.php |
| |
| THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, |
| WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. |
| |
| --*/ |
| |
| #include <sys/ipc.h> |
| #include <sys/shm.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| |
| #include "PiPei.h" |
| #include "Protocol/UnixThunk.h" |
| #include "Protocol/SimpleTextIn.h" |
| #include "Protocol/UgaDraw.h" |
| #include "Protocol/UnixUgaIo.h" |
| #include <X11/Xlib.h> |
| #include <X11/Xutil.h> |
| #include <X11/Xos.h> |
| #include <X11/extensions/XShm.h> |
| #include <X11/keysym.h> |
| |
| #include <Ppi/StatusCode.h> |
| |
| #include <Library/PeCoffLib.h> |
| #include <Library/BaseLib.h> |
| #include <Library/BaseMemoryLib.h> |
| #include <Library/PrintLib.h> |
| #include <Library/PcdLib.h> |
| #include <Library/DebugLib.h> |
| |
| extern void msSleep (unsigned long Milliseconds); |
| |
| /* XQueryPointer */ |
| |
| struct uga_drv_shift_mask |
| { |
| unsigned char shift; |
| unsigned char size; |
| unsigned char csize; |
| }; |
| |
| #define NBR_KEYS 32 |
| typedef struct |
| { |
| EFI_UNIX_UGA_IO_PROTOCOL UgaIo; |
| |
| Display *display; |
| int screen; /* values for window_size in main */ |
| Window win; |
| GC gc; |
| Visual *visual; |
| |
| int depth; |
| unsigned int width; |
| unsigned int height; |
| unsigned int line_bytes; |
| unsigned int pixel_shift; |
| unsigned char *image_data; |
| |
| struct uga_drv_shift_mask r, g, b; |
| |
| int use_shm; |
| XShmSegmentInfo xshm_info; |
| XImage *image; |
| |
| unsigned int key_rd; |
| unsigned int key_wr; |
| unsigned int key_count; |
| EFI_INPUT_KEY keys[NBR_KEYS]; |
| } UGA_IO_PRIVATE; |
| |
| void |
| HandleEvents(UGA_IO_PRIVATE *drv); |
| |
| void |
| fill_shift_mask (struct uga_drv_shift_mask *sm, unsigned long mask) |
| { |
| sm->shift = 0; |
| sm->size = 0; |
| while ((mask & 1) == 0) |
| { |
| mask >>= 1; |
| sm->shift++; |
| } |
| while (mask & 1) |
| { |
| sm->size++; |
| mask >>= 1; |
| } |
| sm->csize = 8 - sm->size; |
| } |
| |
| int |
| TryCreateShmImage(UGA_IO_PRIVATE *drv) |
| { |
| drv->image = XShmCreateImage (drv->display, drv->visual, |
| drv->depth, ZPixmap, NULL, &drv->xshm_info, |
| drv->width, drv->height); |
| if (drv->image == NULL) |
| return 0; |
| |
| switch (drv->image->bitmap_unit) { |
| case 32: |
| drv->pixel_shift = 2; |
| break; |
| case 16: |
| drv->pixel_shift = 1; |
| break; |
| case 8: |
| drv->pixel_shift = 0; |
| break; |
| } |
| |
| drv->xshm_info.shmid = shmget |
| (IPC_PRIVATE, drv->image->bytes_per_line * drv->image->height, |
| IPC_CREAT | 0777); |
| if (drv->xshm_info.shmid < 0) { |
| XDestroyImage(drv->image); |
| return 0; |
| } |
| |
| drv->image_data = shmat (drv->xshm_info.shmid, NULL, 0); |
| if(!drv->image_data) { |
| shmctl (drv->xshm_info.shmid, IPC_RMID, NULL); |
| XDestroyImage(drv->image); |
| return 0; |
| } |
| |
| #ifndef __APPLE__ |
| // |
| // This closes shared memory in real time on OS X. Only closes after folks quit using |
| // it on Linux. |
| // |
| /* Can this fail ? */ |
| shmctl (drv->xshm_info.shmid, IPC_RMID, NULL); |
| #endif |
| |
| drv->xshm_info.shmaddr = (char*)drv->image_data; |
| drv->image->data = (char*)drv->image_data; |
| |
| if (!XShmAttach (drv->display, &drv->xshm_info)) { |
| shmdt (drv->image_data); |
| XDestroyImage(drv->image); |
| return 0; |
| } |
| return 1; |
| } |
| |
| EFI_STATUS |
| UgaClose (EFI_UNIX_UGA_IO_PROTOCOL *UgaIo) |
| { |
| UGA_IO_PRIVATE *drv = (UGA_IO_PRIVATE *)UgaIo; |
| |
| if (drv == NULL) |
| return EFI_SUCCESS; |
| if (drv->image != NULL) |
| { |
| XDestroyImage(drv->image); |
| |
| if (drv->use_shm) |
| shmdt (drv->image_data); |
| |
| drv->image_data = NULL; |
| drv->image = NULL; |
| } |
| XDestroyWindow(drv->display, drv->win); |
| XCloseDisplay(drv->display); |
| |
| #ifdef __APPLE__ |
| // Free up the shared memory |
| shmctl (drv->xshm_info.shmid, IPC_RMID, NULL); |
| #endif |
| |
| free(drv); |
| return EFI_SUCCESS; |
| } |
| |
| EFI_STATUS |
| UgaSize(EFI_UNIX_UGA_IO_PROTOCOL *UgaIo, UINT32 Width, UINT32 Height) |
| { |
| UGA_IO_PRIVATE *drv = (UGA_IO_PRIVATE *)UgaIo; |
| XSizeHints size_hints; |
| |
| /* Destroy current buffer if created. */ |
| if (drv->image != NULL) |
| { |
| /* Before destroy buffer, need to make sure the buffer available for access. */ |
| XDestroyImage(drv->image); |
| |
| if (drv->use_shm) |
| shmdt (drv->image_data); |
| |
| drv->image_data = NULL; |
| drv->image = NULL; |
| } |
| |
| drv->width = Width; |
| drv->height = Height; |
| XResizeWindow (drv->display, drv->win, Width, Height); |
| |
| /* Allocate image. */ |
| if (XShmQueryExtension(drv->display) && TryCreateShmImage(drv)) { |
| drv->use_shm = 1; |
| } else { |
| drv->use_shm = 0; |
| if (drv->depth > 16) |
| drv->pixel_shift = 2; |
| else if (drv->depth > 8) |
| drv->pixel_shift = 1; |
| else |
| drv->pixel_shift = 0; |
| |
| drv->image_data = malloc((drv->width * drv->height) << drv->pixel_shift); |
| drv->image = XCreateImage (drv->display, drv->visual, drv->depth, |
| ZPixmap, 0, (char *)drv->image_data, |
| drv->width, drv->height, |
| 8 << drv->pixel_shift, 0); |
| } |
| drv->line_bytes = drv->image->bytes_per_line; |
| fill_shift_mask (&drv->r, drv->image->red_mask); |
| fill_shift_mask (&drv->g, drv->image->green_mask); |
| fill_shift_mask (&drv->b, drv->image->blue_mask); |
| |
| /* Set WM hints. */ |
| size_hints.flags = PSize | PMinSize | PMaxSize; |
| size_hints.min_width = size_hints.max_width = size_hints.base_width = Width; |
| size_hints.min_height = size_hints.max_height = size_hints.base_height = Height; |
| XSetWMNormalHints (drv->display, drv->win, &size_hints); |
| |
| XMapWindow (drv->display, drv->win); |
| HandleEvents(drv); |
| return EFI_SUCCESS; |
| } |
| |
| void |
| handleKeyEvent(UGA_IO_PRIVATE *drv, XEvent *ev) |
| { |
| KeySym keysym; |
| char str[4]; |
| EFI_INPUT_KEY Key; |
| int res; |
| |
| if (drv->key_count == NBR_KEYS) |
| return; |
| |
| res = XLookupString(&ev->xkey, str, sizeof(str), &keysym, NULL); |
| Key.ScanCode = 0; |
| Key.UnicodeChar = 0; |
| switch (keysym) { |
| case XK_Home: Key.ScanCode = SCAN_HOME; break; |
| case XK_End: Key.ScanCode = SCAN_END; break; |
| case XK_Left: Key.ScanCode = SCAN_LEFT; break; |
| case XK_Right: Key.ScanCode = SCAN_RIGHT; break; |
| case XK_Up: Key.ScanCode = SCAN_UP; break; |
| case XK_Down: Key.ScanCode = SCAN_DOWN; break; |
| case XK_Delete: Key.ScanCode = SCAN_DELETE; break; |
| case XK_Insert: Key.ScanCode = SCAN_INSERT; break; |
| case XK_Page_Up: Key.ScanCode = SCAN_PAGE_UP; break; |
| case XK_Page_Down: Key.ScanCode = SCAN_PAGE_DOWN; break; |
| case XK_Escape: Key.ScanCode = SCAN_ESC; break; |
| |
| case XK_F1: Key.ScanCode = SCAN_F1; break; |
| case XK_F2: Key.ScanCode = SCAN_F2; break; |
| case XK_F3: Key.ScanCode = SCAN_F3; break; |
| case XK_F4: Key.ScanCode = SCAN_F4; break; |
| case XK_F5: Key.ScanCode = SCAN_F5; break; |
| case XK_F6: Key.ScanCode = SCAN_F6; break; |
| case XK_F7: Key.ScanCode = SCAN_F7; break; |
| case XK_F8: Key.ScanCode = SCAN_F8; break; |
| case XK_F9: Key.ScanCode = SCAN_F9; break; |
| |
| default: |
| if (res == 1) { |
| Key.UnicodeChar = str[0]; |
| } else { |
| return; |
| } |
| } |
| |
| drv->keys[drv->key_wr] = Key; |
| drv->key_wr = (drv->key_wr + 1) % NBR_KEYS; |
| drv->key_count++; |
| } |
| |
| void |
| Redraw(UGA_IO_PRIVATE *drv, UINTN X, UINTN Y, UINTN Width, UINTN Height) |
| { |
| if (drv->use_shm) |
| XShmPutImage (drv->display, drv->win, drv->gc, drv->image, |
| X, Y, X, Y, Width, Height, False); |
| else |
| XPutImage (drv->display, drv->win, drv->gc, drv->image, |
| X, Y, X, Y, Width, Height); |
| XFlush(drv->display); |
| } |
| |
| void |
| HandleEvent(UGA_IO_PRIVATE *drv, XEvent *ev) |
| { |
| switch (ev->type) |
| { |
| case Expose: |
| Redraw(drv, ev->xexpose.x, ev->xexpose.y, |
| ev->xexpose.width, ev->xexpose.height); |
| break; |
| case GraphicsExpose: |
| Redraw(drv, ev->xgraphicsexpose.x, ev->xgraphicsexpose.y, |
| ev->xgraphicsexpose.width, ev->xgraphicsexpose.height); |
| break; |
| case KeyPress: |
| handleKeyEvent(drv, ev); |
| break; |
| case MappingNotify: |
| XRefreshKeyboardMapping(&ev->xmapping); |
| break; |
| #if 0 |
| case DestroyNotify: |
| XCloseDisplay (drv->display); |
| exit (1); |
| break; |
| #endif |
| case NoExpose: |
| default: |
| break; |
| } |
| } |
| |
| void |
| HandleEvents(UGA_IO_PRIVATE *drv) |
| { |
| while (XPending(drv->display) != 0) |
| { |
| XEvent ev; |
| |
| XNextEvent (drv->display, &ev); |
| HandleEvent(drv, &ev); |
| } |
| } |
| |
| unsigned long |
| UgaPixelToColor (UGA_IO_PRIVATE *drv, EFI_UGA_PIXEL pixel) |
| { |
| return ((pixel.Red >> drv->r.csize) << drv->r.shift) |
| | ((pixel.Green >> drv->g.csize) << drv->g.shift) |
| | ((pixel.Blue >> drv->b.csize) << drv->b.shift); |
| } |
| |
| EFI_UGA_PIXEL |
| UgaColorToPixel (UGA_IO_PRIVATE *drv, unsigned long val) |
| { |
| EFI_UGA_PIXEL res; |
| |
| memset (&res, 0, sizeof (EFI_UGA_PIXEL)); |
| /* FIXME: should round instead of truncate. */ |
| res.Red = (val >> drv->r.shift) << drv->r.csize; |
| res.Green = (val >> drv->g.shift) << drv->g.csize; |
| res.Blue = (val >> drv->b.shift) << drv->b.csize; |
| |
| return res; |
| } |
| |
| EFI_STATUS |
| UgaCheckKey(EFI_UNIX_UGA_IO_PROTOCOL *UgaIo) |
| { |
| UGA_IO_PRIVATE *drv = (UGA_IO_PRIVATE *)UgaIo; |
| HandleEvents(drv); |
| if (drv->key_count != 0) |
| return EFI_SUCCESS; |
| else { |
| /* EFI is certainly polling. Be CPU-friendly. */ |
| msSleep (20); |
| return EFI_NOT_READY; |
| } |
| } |
| |
| EFI_STATUS |
| UgaGetKey (EFI_UNIX_UGA_IO_PROTOCOL *UgaIo, EFI_INPUT_KEY *key) |
| { |
| UGA_IO_PRIVATE *drv = (UGA_IO_PRIVATE *)UgaIo; |
| EFI_STATUS status; |
| |
| status = UgaCheckKey(UgaIo); |
| if (status != EFI_SUCCESS) |
| return status; |
| |
| *key = drv->keys[drv->key_rd]; |
| drv->key_rd = (drv->key_rd + 1) % NBR_KEYS; |
| drv->key_count--; |
| return EFI_SUCCESS; |
| } |
| |
| EFI_STATUS |
| UgaBlt(EFI_UNIX_UGA_IO_PROTOCOL *UgaIo, |
| IN EFI_UGA_PIXEL *BltBuffer OPTIONAL, |
| IN EFI_UGA_BLT_OPERATION BltOperation, |
| IN UINTN SourceX, |
| IN UINTN SourceY, |
| IN UINTN DestinationX, |
| IN UINTN DestinationY, |
| IN UINTN Width, |
| IN UINTN Height, |
| IN UINTN Delta OPTIONAL |
| ) |
| { |
| UGA_IO_PRIVATE *Private = (UGA_IO_PRIVATE *)UgaIo; |
| UINTN DstY; |
| UINTN SrcY; |
| UINTN DstX; |
| UINTN SrcX; |
| UINTN Index; |
| EFI_UGA_PIXEL *Blt; |
| UINT8 *Dst; |
| UINT8 *Src; |
| UINTN Nbr; |
| unsigned long Color; |
| |
| // |
| // Check bounds |
| // |
| if (BltOperation == EfiUgaVideoToBltBuffer |
| || BltOperation == EfiUgaVideoToVideo) { |
| // |
| // Source is Video. |
| // |
| if (SourceY + Height > Private->height) { |
| return EFI_INVALID_PARAMETER; |
| } |
| |
| if (SourceX + Width > Private->width) { |
| return EFI_INVALID_PARAMETER; |
| } |
| } |
| |
| if (BltOperation == EfiUgaBltBufferToVideo |
| || BltOperation == EfiUgaVideoToVideo |
| || BltOperation == EfiUgaVideoFill) { |
| // |
| // Destination is Video |
| // |
| if (DestinationY + Height > Private->height) { |
| return EFI_INVALID_PARAMETER; |
| } |
| |
| if (DestinationX + Width > Private->width) { |
| return EFI_INVALID_PARAMETER; |
| } |
| } |
| |
| switch (BltOperation) { |
| case EfiUgaVideoToBltBuffer: |
| Blt = (EFI_UGA_PIXEL *)((UINT8 *)BltBuffer + (DestinationY * Delta) + DestinationX * sizeof (EFI_UGA_PIXEL)); |
| Delta -= Width * sizeof (EFI_UGA_PIXEL); |
| for (SrcY = SourceY; SrcY < (Height + SourceY); SrcY++) { |
| for (SrcX = SourceX; SrcX < (Width + SourceX); SrcX++) { |
| *Blt++ = UgaColorToPixel(Private, |
| XGetPixel(Private->image, SrcX, SrcY)); |
| } |
| Blt = (EFI_UGA_PIXEL *) ((UINT8 *) Blt + Delta); |
| } |
| break; |
| case EfiUgaBltBufferToVideo: |
| Blt = (EFI_UGA_PIXEL *)((UINT8 *)BltBuffer + (SourceY * Delta) + SourceX * sizeof (EFI_UGA_PIXEL)); |
| Delta -= Width * sizeof (EFI_UGA_PIXEL); |
| for (DstY = DestinationY; DstY < (Height + DestinationY); DstY++) { |
| for (DstX = DestinationX; DstX < (Width + DestinationX); DstX++) { |
| XPutPixel(Private->image, DstX, DstY, UgaPixelToColor(Private, *Blt)); |
| Blt++; |
| } |
| Blt = (EFI_UGA_PIXEL *) ((UINT8 *) Blt + Delta); |
| } |
| break; |
| case EfiUgaVideoToVideo: |
| Dst = Private->image_data + (DestinationX << Private->pixel_shift) |
| + DestinationY * Private->line_bytes; |
| Src = Private->image_data + (SourceX << Private->pixel_shift) |
| + SourceY * Private->line_bytes; |
| Nbr = Width << Private->pixel_shift; |
| if (DestinationY < SourceY) { |
| for (Index = 0; Index < Height; Index++) { |
| memcpy (Dst, Src, Nbr); |
| Dst += Private->line_bytes; |
| Src += Private->line_bytes; |
| } |
| } |
| else { |
| Dst += (Height - 1) * Private->line_bytes; |
| Src += (Height - 1) * Private->line_bytes; |
| for (Index = 0; Index < Height; Index++) { |
| // |
| // Source and Destination Y may be equal, therefore Dst and Src may |
| // overlap. |
| // |
| memmove (Dst, Src, Nbr); |
| Dst -= Private->line_bytes; |
| Src -= Private->line_bytes; |
| } |
| } |
| break; |
| case EfiUgaVideoFill: |
| Color = UgaPixelToColor(Private, *BltBuffer); |
| for (DstY = DestinationY; DstY < (Height + DestinationY); DstY++) { |
| for (DstX = DestinationX; DstX < (Width + DestinationX); DstX++) { |
| XPutPixel(Private->image, DstX, DstY, Color); |
| } |
| } |
| break; |
| default: |
| return EFI_INVALID_PARAMETER; |
| } |
| |
| // |
| // Refresh screen. |
| // |
| switch (BltOperation) { |
| case EfiUgaVideoToVideo: |
| XCopyArea(Private->display, Private->win, Private->win, Private->gc, |
| SourceX, SourceY, Width, Height, DestinationX, DestinationY); |
| while (1) { |
| XEvent ev; |
| |
| XNextEvent (Private->display, &ev); |
| HandleEvent(Private, &ev); |
| if (ev.type == NoExpose || ev.type == GraphicsExpose) |
| break; |
| } |
| break; |
| case EfiUgaVideoFill: |
| Color = UgaPixelToColor(Private, *BltBuffer); |
| XSetForeground(Private->display, Private->gc, Color); |
| XFillRectangle(Private->display, Private->win, Private->gc, |
| DestinationX, DestinationY, Width, Height); |
| XFlush(Private->display); |
| break; |
| case EfiUgaBltBufferToVideo: |
| Redraw(Private, DestinationX, DestinationY, Width, Height); |
| break; |
| default: |
| break; |
| } |
| return EFI_SUCCESS; |
| } |
| |
| EFI_STATUS |
| UgaCreate (EFI_UNIX_UGA_IO_PROTOCOL **Uga, CONST CHAR16 *Title) |
| { |
| UGA_IO_PRIVATE *drv; |
| unsigned int border_width = 0; |
| char *display_name = NULL; |
| int title_len; |
| |
| drv = (UGA_IO_PRIVATE *)calloc (1, sizeof (UGA_IO_PRIVATE)); |
| if (drv == NULL) |
| return EFI_OUT_OF_RESOURCES; |
| |
| #ifdef __APPLE__ |
| // |
| // |
| // |
| EFI_STATUS EFIAPI GasketUgaClose (EFI_UNIX_UGA_IO_PROTOCOL *UgaIo); |
| EFI_STATUS EFIAPI GasketUgaSize (EFI_UNIX_UGA_IO_PROTOCOL *UgaIo, UINT32 Width, UINT32 Height); |
| EFI_STATUS EFIAPI GasketUgaCheckKey (EFI_UNIX_UGA_IO_PROTOCOL *UgaIo); |
| EFI_STATUS EFIAPI GasketUgaGetKey (EFI_UNIX_UGA_IO_PROTOCOL *UgaIo, EFI_INPUT_KEY *key); |
| EFI_STATUS EFIAPI GasketUgaBlt ( |
| EFI_UNIX_UGA_IO_PROTOCOL *UgaIo, |
| IN EFI_UGA_PIXEL *BltBuffer OPTIONAL, |
| IN EFI_UGA_BLT_OPERATION BltOperation, |
| IN UINTN SourceX, |
| IN UINTN SourceY, |
| IN UINTN DestinationX, |
| IN UINTN DestinationY, |
| IN UINTN Width, |
| IN UINTN Height, |
| IN UINTN Delta OPTIONAL |
| ); |
| |
| drv->UgaIo.UgaClose = GasketUgaClose; |
| drv->UgaIo.UgaSize = GasketUgaSize; |
| drv->UgaIo.UgaCheckKey = GasketUgaCheckKey; |
| drv->UgaIo.UgaGetKey = GasketUgaGetKey; |
| drv->UgaIo.UgaBlt = GasketUgaBlt; |
| #else |
| drv->UgaIo.UgaClose = UgaClose; |
| drv->UgaIo.UgaSize = UgaSize; |
| drv->UgaIo.UgaCheckKey = UgaCheckKey; |
| drv->UgaIo.UgaGetKey = UgaGetKey; |
| drv->UgaIo.UgaBlt = UgaBlt; |
| #endif |
| |
| |
| |
| drv->key_count = 0; |
| drv->key_rd = 0; |
| drv->key_wr = 0; |
| drv->display = XOpenDisplay (display_name); |
| if (drv->display == NULL) |
| { |
| fprintf (stderr, "uga: cannot connect to X server %s\n", |
| XDisplayName (display_name)); |
| free (drv); |
| return EFI_DEVICE_ERROR; |
| } |
| drv->screen = DefaultScreen (drv->display); |
| drv->visual = DefaultVisual (drv->display, drv->screen); |
| drv->win = XCreateSimpleWindow |
| (drv->display, RootWindow (drv->display, drv->screen), |
| 0, 0, 4, 4, border_width, |
| WhitePixel (drv->display, drv->screen), |
| BlackPixel (drv->display, drv->screen)); |
| |
| drv->depth = DefaultDepth (drv->display, drv->screen); |
| |
| /* Compute title len and convert to Ascii. */ |
| for (title_len = 0; Title[title_len] != 0; title_len++) |
| ; |
| { |
| char title[title_len + 1]; |
| int i; |
| for (i = 0; i < title_len; i++) |
| title[i] = Title[i]; |
| title[i] = 0; |
| |
| XStoreName (drv->display, drv->win, title); |
| } |
| |
| XSelectInput (drv->display, drv->win, |
| ExposureMask | KeyPressMask); |
| drv->gc = DefaultGC (drv->display, drv->screen); |
| |
| *Uga = (EFI_UNIX_UGA_IO_PROTOCOL *)drv; |
| return EFI_SUCCESS; |
| } |