/* Haiku window system support. Hey, Emacs, this is -*- C++ -*- Copyright (C) 2021-2022 Free Software Foundation, Inc. This file is part of GNU Emacs. GNU Emacs is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. GNU Emacs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with GNU Emacs. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef USE_BE_CAIRO #include #endif #include "haiku_support.h" #define SCROLL_BAR_UPDATE 3000 #define WAIT_FOR_RELEASE 3001 static color_space dpy_color_space = B_NO_COLOR_SPACE; static key_map *key_map = NULL; static char *key_chars = NULL; static BLocker key_map_lock; /* The locking semantics of BWindows running in multiple threads are so complex that child frame state (which is the only state that is shared between different BWindows at runtime) does best with a single global lock. */ static BLocker child_frame_lock; /* A LeaveNotify event (well, the closest equivalent on Haiku, which is a B_MOUSE_MOVED event with `transit' set to B_EXITED_VIEW) might be sent out-of-order with regards to motion events from other windows, such as when the mouse pointer rapidly moves from an undecorated child frame to its parent. This can cause a failure to clear the mouse face on the former if an event for the latter is read by Emacs first and ends up showing the mouse face there. While this lock doesn't really ensure that the events will be delivered in the correct order, it makes them arrive in the correct order "most of the time" on my machine, which is good enough and preferable to adding a lot of extra complexity to the event handling code to sort motion events by their timestamps. Obviously this depends on the number of execution units that are available, and the scheduling priority of each thread involved in the input handling, but it will be good enough for most people. */ static BLocker movement_locker; static BMessage volatile *popup_track_message; static int32 volatile alert_popup_value; static void *grab_view = NULL; static BLocker grab_view_locker; /* This could be a private API, but it's used by (at least) the Qt port, so it's probably here to stay. */ extern status_t get_subpixel_antialiasing (bool *); extern "C" { extern _Noreturn void emacs_abort (void); /* Also defined in haikuterm.h. */ extern void be_app_quit (void); } static thread_id app_thread; _Noreturn void gui_abort (const char *msg) { fprintf (stderr, "Abort in GUI code: %s\n", msg); fprintf (stderr, "Under Haiku, Emacs cannot recover from errors in GUI code\n"); fprintf (stderr, "App Server disconnects usually manifest as bitmap " "initialization failures or lock failures."); emacs_abort (); } struct be_popup_menu_data { int x, y; BPopUpMenu *menu; }; static int32 be_popup_menu_thread_entry (void *thread_data) { struct be_popup_menu_data *data; struct haiku_dummy_event dummy; BMenuItem *it; data = (struct be_popup_menu_data *) thread_data; it = data->menu->Go (BPoint (data->x, data->y)); if (it) popup_track_message = it->Message (); else popup_track_message = NULL; haiku_write (DUMMY_EVENT, &dummy); return 0; } /* Convert a raw character RAW produced by the keycode KEY into a key symbol and place it in KEYSYM. If RAW cannot be converted into a keysym, value is 0. If RAW can be converted into a keysym, but it should be ignored, value is -1. Any other value means success, and that the keysym should be used instead of mapping the keycode into a character. */ static int keysym_from_raw_char (int32 raw, int32 key, unsigned *code) { switch (raw) { case B_BACKSPACE: *code = XK_BackSpace; break; case B_RETURN: *code = XK_Return; break; case B_TAB: *code = XK_Tab; break; case B_ESCAPE: *code = XK_Escape; break; case B_LEFT_ARROW: *code = XK_Left; break; case B_RIGHT_ARROW: *code = XK_Right; break; case B_UP_ARROW: *code = XK_Up; break; case B_DOWN_ARROW: *code = XK_Down; break; case B_INSERT: *code = XK_Insert; break; case B_DELETE: *code = XK_Delete; break; case B_HOME: *code = XK_Home; break; case B_END: *code = XK_End; break; case B_PAGE_UP: *code = XK_Page_Up; break; case B_PAGE_DOWN: *code = XK_Page_Down; break; case B_FUNCTION_KEY: *code = XK_F1 + key - 2; if (*code - XK_F1 == 12) *code = XK_Print; else if (*code - XK_F1 == 13) /* Okay, Scroll Lock is a bit too much: keyboard.c doesn't know about it yet, and it shouldn't, since that's a modifier key. *code = XK_Scroll_Lock; */ return -1; else if (*code - XK_F1 == 14) *code = XK_Pause; break; default: return 0; } return 1; } static void map_key (char *chars, int32 offset, uint32_t *c) { int size = chars[offset++]; switch (size) { case 0: break; case 1: *c = chars[offset]; break; default: { char str[5]; int i = (size <= 4) ? size : 4; strncpy (str, &(chars[offset]), i); str[i] = '0'; *c = BUnicodeChar::FromUTF8 ((char *) &str); break; } } } static void map_shift (uint32_t kc, uint32_t *ch) { if (!key_map_lock.Lock ()) gui_abort ("Failed to lock keymap"); if (!key_map) get_key_map (&key_map, &key_chars); if (!key_map) return; if (kc >= 128) return; int32_t m = key_map->shift_map[kc]; map_key (key_chars, m, ch); key_map_lock.Unlock (); } static void map_caps (uint32_t kc, uint32_t *ch) { if (!key_map_lock.Lock ()) gui_abort ("Failed to lock keymap"); if (!key_map) get_key_map (&key_map, &key_chars); if (!key_map) return; if (kc >= 128) return; int32_t m = key_map->caps_map[kc]; map_key (key_chars, m, ch); key_map_lock.Unlock (); } static void map_caps_shift (uint32_t kc, uint32_t *ch) { if (!key_map_lock.Lock ()) gui_abort ("Failed to lock keymap"); if (!key_map) get_key_map (&key_map, &key_chars); if (!key_map) return; if (kc >= 128) return; int32_t m = key_map->caps_shift_map[kc]; map_key (key_chars, m, ch); key_map_lock.Unlock (); } static void map_normal (uint32_t kc, uint32_t *ch) { if (!key_map_lock.Lock ()) gui_abort ("Failed to lock keymap"); if (!key_map) get_key_map (&key_map, &key_chars); if (!key_map) return; if (kc >= 128) return; int32_t m = key_map->normal_map[kc]; map_key (key_chars, m, ch); key_map_lock.Unlock (); } class Emacs : public BApplication { public: BMessage settings; bool settings_valid_p = false; Emacs () : BApplication ("application/x-vnd.GNU-emacs") { BPath settings_path; if (find_directory (B_USER_SETTINGS_DIRECTORY, &settings_path) != B_OK) return; settings_path.Append (PACKAGE_NAME); BEntry entry (settings_path.Path ()); BFile settings_file (&entry, B_READ_ONLY | B_CREATE_FILE); if (settings.Unflatten (&settings_file) != B_OK) return; settings_valid_p = true; } void AboutRequested (void) { BAlert *about = new BAlert (PACKAGE_NAME, PACKAGE_STRING "\nThe extensible, self-documenting, real-time display editor.", "Close"); about->Go (); } bool QuitRequested (void) { struct haiku_app_quit_requested_event rq; haiku_write (APP_QUIT_REQUESTED_EVENT, &rq); return 0; } }; class EmacsWindow : public BWindow { public: struct child_frame { struct child_frame *next; int xoff, yoff; EmacsWindow *window; } *subset_windows = NULL; EmacsWindow *parent = NULL; BRect pre_fullscreen_rect; BRect pre_zoom_rect; int x_before_zoom = INT_MIN; int y_before_zoom = INT_MIN; int fullscreen_p = 0; int zoomed_p = 0; int shown_flag = 0; volatile int was_shown_p = 0; bool menu_bar_active_p = false; bool override_redirect_p = false; window_look pre_override_redirect_look; window_feel pre_override_redirect_feel; uint32 pre_override_redirect_workspaces; pthread_mutex_t menu_update_mutex = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t menu_update_cv = PTHREAD_COND_INITIALIZER; bool menu_updated_p = false; EmacsWindow () : BWindow (BRect (0, 0, 0, 0), "", B_TITLED_WINDOW_LOOK, B_NORMAL_WINDOW_FEEL, B_NO_SERVER_SIDE_WINDOW_MODIFIERS) { } ~EmacsWindow () { if (!child_frame_lock.Lock ()) gui_abort ("Failed to lock child frame state lock"); struct child_frame *next; for (struct child_frame *f = subset_windows; f; f = next) { if (f->window->LockLooper ()) gui_abort ("Failed to lock looper for unparent"); f->window->Unparent (); f->window->UnlockLooper (); next = f->next; delete f; } if (this->parent) UnparentAndUnlink (); child_frame_lock.Unlock (); pthread_cond_destroy (&menu_update_cv); pthread_mutex_destroy (&menu_update_mutex); } void UpwardsSubset (EmacsWindow *w) { for (; w; w = w->parent) AddToSubset (w); } void UpwardsSubsetChildren (EmacsWindow *w) { if (!LockLooper ()) gui_abort ("Failed to lock looper for subset"); if (!child_frame_lock.Lock ()) gui_abort ("Failed to lock child frame state lock"); UpwardsSubset (w); for (struct child_frame *f = subset_windows; f; f = f->next) f->window->UpwardsSubsetChildren (w); child_frame_lock.Unlock (); UnlockLooper (); } void UpwardsUnSubset (EmacsWindow *w) { for (; w; w = w->parent) RemoveFromSubset (w); } void UpwardsUnSubsetChildren (EmacsWindow *w) { if (!LockLooper ()) gui_abort ("Failed to lock looper for unsubset"); if (!child_frame_lock.Lock ()) gui_abort ("Failed to lock child frame state lock"); UpwardsUnSubset (w); for (struct child_frame *f = subset_windows; f; f = f->next) f->window->UpwardsUnSubsetChildren (w); child_frame_lock.Unlock (); UnlockLooper (); } void Unparent (void) { if (!child_frame_lock.Lock ()) gui_abort ("Failed to lock child frame state lock"); this->SetFeel (B_NORMAL_WINDOW_FEEL); UpwardsUnSubsetChildren (parent); this->RemoveFromSubset (this); this->parent = NULL; if (fullscreen_p) { fullscreen_p = 0; MakeFullscreen (1); } child_frame_lock.Unlock (); } void UnparentAndUnlink (void) { if (!child_frame_lock.Lock ()) gui_abort ("Failed to lock child frame state lock"); this->parent->UnlinkChild (this); this->Unparent (); child_frame_lock.Unlock (); } void UnlinkChild (EmacsWindow *window) { struct child_frame *last = NULL; struct child_frame *tem = subset_windows; for (; tem; last = tem, tem = tem->next) { if (tem->window == window) { if (last) last->next = tem->next; else subset_windows = tem->next; delete tem; return; } } gui_abort ("Failed to unlink child frame"); } void ParentTo (EmacsWindow *window) { if (!child_frame_lock.Lock ()) gui_abort ("Failed to lock child frame state lock"); if (this->parent) UnparentAndUnlink (); this->parent = window; this->SetFeel (B_FLOATING_SUBSET_WINDOW_FEEL); this->AddToSubset (this); if (!IsHidden () && this->parent) UpwardsSubsetChildren (parent); if (fullscreen_p) { fullscreen_p = 0; MakeFullscreen (1); } window->LinkChild (this); child_frame_lock.Unlock (); } void LinkChild (EmacsWindow *window) { struct child_frame *f = new struct child_frame; for (struct child_frame *f = subset_windows; f; f = f->next) { if (window == f->window) gui_abort ("Trying to link a child frame that is already present"); } f->window = window; f->next = subset_windows; f->xoff = -1; f->yoff = -1; subset_windows = f; } void DoMove (struct child_frame *f) { BRect frame = this->Frame (); f->window->MoveTo (frame.left + f->xoff, frame.top + f->yoff); } void DoUpdateWorkspace (struct child_frame *f) { f->window->SetWorkspaces (this->Workspaces ()); } void MoveChild (EmacsWindow *window, int xoff, int yoff, int weak_p) { if (!child_frame_lock.Lock ()) gui_abort ("Failed to lock child frame state lock"); for (struct child_frame *f = subset_windows; f; f = f->next) { if (window == f->window) { f->xoff = xoff; f->yoff = yoff; if (!weak_p) DoMove (f); child_frame_lock.Unlock (); return; } } child_frame_lock.Unlock (); gui_abort ("Trying to move a child frame that doesn't exist"); } void WindowActivated (bool activated) { struct haiku_activation_event rq; rq.window = this; rq.activated_p = activated; haiku_write (ACTIVATION, &rq); } void MessageReceived (BMessage *msg) { int32 old_what = 0; if (msg->WasDropped ()) { BPoint whereto; struct haiku_drag_and_drop_event rq; if (msg->FindPoint ("_drop_point_", &whereto) == B_OK) { this->ConvertFromScreen (&whereto); rq.window = this; rq.message = DetachCurrentMessage ();; rq.x = whereto.x; rq.y = whereto.y; haiku_write (DRAG_AND_DROP_EVENT, &rq); } } else if (msg->GetPointer ("menuptr")) { struct haiku_menu_bar_select_event rq; rq.window = this; rq.ptr = (void *) msg->GetPointer ("menuptr"); haiku_write (MENU_BAR_SELECT_EVENT, &rq); } else if (msg->what == 'FPSE' || ((msg->FindInt32 ("old_what", &old_what) == B_OK && old_what == 'FPSE'))) { struct haiku_file_panel_event rq; BEntry entry; BPath path; entry_ref ref; rq.ptr = NULL; if (msg->FindRef ("refs", &ref) == B_OK && entry.SetTo (&ref, 0) == B_OK && entry.GetPath (&path) == B_OK) { const char *str_path = path.Path (); if (str_path) rq.ptr = strdup (str_path); } if (msg->FindRef ("directory", &ref), entry.SetTo (&ref, 0) == B_OK && entry.GetPath (&path) == B_OK) { const char *name = msg->GetString ("name"); const char *str_path = path.Path (); if (name) { char str_buf[std::strlen (str_path) + std::strlen (name) + 2]; snprintf ((char *) &str_buf, std::strlen (str_path) + std::strlen (name) + 2, "%s/%s", str_path, name); rq.ptr = strdup (str_buf); } } haiku_write (FILE_PANEL_EVENT, &rq); } else BWindow::MessageReceived (msg); } void DispatchMessage (BMessage *msg, BHandler *handler) { if (msg->what == B_KEY_DOWN || msg->what == B_KEY_UP) { struct haiku_key_event rq; /* Pass through key events to the regular dispatch mechanism if the menu bar active, so that key navigation can work. */ if (menu_bar_active_p) { BWindow::DispatchMessage (msg, handler); return; } rq.window = this; int32 raw, key; int ret; msg->FindInt32 ("raw_char", &raw); msg->FindInt32 ("key", &key); msg->FindInt64 ("when", &rq.time); rq.modifiers = 0; uint32_t mods = modifiers (); if (mods & B_SHIFT_KEY) rq.modifiers |= HAIKU_MODIFIER_SHIFT; if (mods & B_CONTROL_KEY) rq.modifiers |= HAIKU_MODIFIER_CTRL; if (mods & B_COMMAND_KEY) rq.modifiers |= HAIKU_MODIFIER_ALT; if (mods & B_OPTION_KEY) rq.modifiers |= HAIKU_MODIFIER_SUPER; ret = keysym_from_raw_char (raw, key, &rq.keysym); if (!ret) rq.keysym = 0; if (ret < 0) return; rq.multibyte_char = 0; if (!rq.keysym) { if (mods & B_SHIFT_KEY) { if (mods & B_CAPS_LOCK) map_caps_shift (key, &rq.multibyte_char); else map_shift (key, &rq.multibyte_char); } else { if (mods & B_CAPS_LOCK) map_caps (key, &rq.multibyte_char); else map_normal (key, &rq.multibyte_char); } } haiku_write (msg->what == B_KEY_DOWN ? KEY_DOWN : KEY_UP, &rq); } else if (msg->what == B_MOUSE_WHEEL_CHANGED) { struct haiku_wheel_move_event rq; rq.window = this; rq.modifiers = 0; uint32_t mods = modifiers (); if (mods & B_SHIFT_KEY) rq.modifiers |= HAIKU_MODIFIER_SHIFT; if (mods & B_CONTROL_KEY) rq.modifiers |= HAIKU_MODIFIER_CTRL; if (mods & B_COMMAND_KEY) rq.modifiers |= HAIKU_MODIFIER_ALT; if (mods & B_OPTION_KEY) rq.modifiers |= HAIKU_MODIFIER_SUPER; float dx, dy; if (msg->FindFloat ("be:wheel_delta_x", &dx) == B_OK && msg->FindFloat ("be:wheel_delta_y", &dy) == B_OK) { rq.delta_x = dx; rq.delta_y = dy; haiku_write (WHEEL_MOVE_EVENT, &rq); }; } else BWindow::DispatchMessage (msg, handler); } void MenusBeginning () { struct haiku_menu_bar_state_event rq; int lock_count = 0; thread_id current_thread = find_thread (NULL); thread_id window_thread = Thread (); rq.window = this; rq.no_lock = false; if (window_thread != current_thread) rq.no_lock = true; haiku_write (MENU_BAR_OPEN, &rq); if (!rq.no_lock) { while (IsLocked ()) { ++lock_count; UnlockLooper (); } pthread_mutex_lock (&menu_update_mutex); while (!menu_updated_p) pthread_cond_wait (&menu_update_cv, &menu_update_mutex); menu_updated_p = false; pthread_mutex_unlock (&menu_update_mutex); for (; lock_count; --lock_count) { if (!LockLooper ()) gui_abort ("Failed to lock after cv signal denoting menu update"); } } menu_bar_active_p = true; } void MenusEnded () { struct haiku_menu_bar_state_event rq; rq.window = this; haiku_write (MENU_BAR_CLOSE, &rq); menu_bar_active_p = false; } void FrameResized (float newWidth, float newHeight) { struct haiku_resize_event rq; rq.window = this; rq.px_heightf = newHeight + 1.0f; rq.px_widthf = newWidth + 1.0f; haiku_write (FRAME_RESIZED, &rq); BWindow::FrameResized (newWidth, newHeight); } void FrameMoved (BPoint newPosition) { struct haiku_move_event rq; rq.window = this; rq.x = std::lrint (newPosition.x); rq.y = std::lrint (newPosition.y); haiku_write (MOVE_EVENT, &rq); if (!child_frame_lock.Lock ()) gui_abort ("Failed to lock child frame state lock"); for (struct child_frame *f = subset_windows; f; f = f->next) DoMove (f); child_frame_lock.Unlock (); BWindow::FrameMoved (newPosition); } void WorkspacesChanged (uint32_t old, uint32_t n) { if (!child_frame_lock.Lock ()) gui_abort ("Failed to lock child frames for changing workspaces"); for (struct child_frame *f = subset_windows; f; f = f->next) DoUpdateWorkspace (f); child_frame_lock.Unlock (); } void EmacsMoveTo (int x, int y) { if (!child_frame_lock.Lock ()) gui_abort ("Failed to lock child frame state lock"); if (!this->parent) this->MoveTo (x, y); else this->parent->MoveChild (this, x, y, 0); child_frame_lock.Unlock (); } bool QuitRequested () { struct haiku_quit_requested_event rq; rq.window = this; haiku_write (QUIT_REQUESTED, &rq); return false; } void Minimize (bool minimized_p) { BWindow::Minimize (minimized_p); struct haiku_iconification_event rq; rq.window = this; rq.iconified_p = !parent && minimized_p; haiku_write (ICONIFICATION, &rq); } void EmacsHide (void) { if (this->IsHidden ()) return; if (!child_frame_lock.Lock ()) gui_abort ("Failed to lock child frame state lock"); Hide (); if (this->parent) UpwardsUnSubsetChildren (this->parent); child_frame_lock.Unlock (); } void EmacsShow (void) { if (!this->IsHidden ()) return; if (!child_frame_lock.Lock ()) gui_abort ("Failed to lock child frame state lock"); if (!was_shown_p) { /* This window is being shown for the first time, which means Show will unlock the looper. In this case, it should be locked again, since the looper is unlocked when the window is first created. */ if (!LockLooper ()) gui_abort ("Failed to lock looper during first window show"); was_shown_p = true; } if (this->parent) shown_flag = 1; Show (); if (this->parent) UpwardsSubsetChildren (this->parent); child_frame_lock.Unlock (); } void Zoom (BPoint o, float w, float h) { struct haiku_zoom_event rq; rq.window = this; rq.x = o.x; rq.y = o.y; rq.width = w + 1; rq.height = h + 1; if (fullscreen_p) MakeFullscreen (0); if (o.x != x_before_zoom || o.y != y_before_zoom) { x_before_zoom = Frame ().left; y_before_zoom = Frame ().top; pre_zoom_rect = Frame (); zoomed_p = 1; haiku_write (ZOOM_EVENT, &rq); } else { zoomed_p = 0; x_before_zoom = y_before_zoom = INT_MIN; } BWindow::Zoom (o, w, h); } void UnZoom (void) { if (!zoomed_p) return; zoomed_p = 0; EmacsMoveTo (pre_zoom_rect.left, pre_zoom_rect.top); ResizeTo (BE_RECT_WIDTH (pre_zoom_rect) - 1, BE_RECT_HEIGHT (pre_zoom_rect) - 1); } void GetParentWidthHeight (int *width, int *height) { if (!child_frame_lock.Lock ()) gui_abort ("Failed to lock child frame state lock"); if (parent) { BRect frame = parent->Frame (); *width = BE_RECT_WIDTH (frame); *height = BE_RECT_HEIGHT (frame); } else { BScreen s (this); BRect frame = s.Frame (); *width = BE_RECT_WIDTH (frame); *height = BE_RECT_HEIGHT (frame); } child_frame_lock.Unlock (); } void OffsetChildRect (BRect *r, EmacsWindow *c) { if (!child_frame_lock.Lock ()) gui_abort ("Failed to lock child frame state lock"); for (struct child_frame *f; f; f = f->next) if (f->window == c) { r->top -= f->yoff; r->bottom -= f->yoff; r->left -= f->xoff; r->right -= f->xoff; child_frame_lock.Unlock (); return; } child_frame_lock.Lock (); gui_abort ("Trying to calculate offsets for a child frame that doesn't exist"); } void MakeFullscreen (int make_fullscreen_p) { BScreen screen (this); if (!screen.IsValid ()) gui_abort ("Trying to make a window fullscreen without a screen"); if (make_fullscreen_p == fullscreen_p) return; fullscreen_p = make_fullscreen_p; uint32 flags = Flags (); if (fullscreen_p) { if (zoomed_p) UnZoom (); flags |= B_NOT_MOVABLE | B_NOT_ZOOMABLE; pre_fullscreen_rect = Frame (); if (!child_frame_lock.Lock ()) gui_abort ("Failed to lock child frame state lock"); if (parent) parent->OffsetChildRect (&pre_fullscreen_rect, this); child_frame_lock.Unlock (); int w, h; EmacsMoveTo (0, 0); GetParentWidthHeight (&w, &h); ResizeTo (w - 1, h - 1); } else { flags &= ~(B_NOT_MOVABLE | B_NOT_ZOOMABLE); EmacsMoveTo (pre_fullscreen_rect.left, pre_fullscreen_rect.top); ResizeTo (BE_RECT_WIDTH (pre_fullscreen_rect) - 1, BE_RECT_HEIGHT (pre_fullscreen_rect) - 1); } SetFlags (flags); } }; class EmacsMenuBar : public BMenuBar { public: EmacsMenuBar () : BMenuBar (BRect (0, 0, 0, 0), NULL) { } void AttachedToWindow (void) { BWindow *window = Window (); window->SetKeyMenuBar (this); } void FrameResized (float newWidth, float newHeight) { struct haiku_menu_bar_resize_event rq; rq.window = this->Window (); rq.height = std::lrint (newHeight); rq.width = std::lrint (newWidth); haiku_write (MENU_BAR_RESIZE, &rq); BMenuBar::FrameResized (newWidth, newHeight); } void MouseMoved (BPoint point, uint32 transit, const BMessage *msg) { struct haiku_menu_bar_left_event rq; if (transit == B_EXITED_VIEW) { rq.x = std::lrint (point.x); rq.y = std::lrint (point.y); rq.window = this->Window (); if (movement_locker.Lock ()) { haiku_write (MENU_BAR_LEFT, &rq); movement_locker.Unlock (); } } BMenuBar::MouseMoved (point, transit, msg); } }; class EmacsView : public BView { public: uint32_t previous_buttons = 0; int looper_locked_count = 0; BRegion sb_region; BRegion invalid_region; BView *offscreen_draw_view = NULL; BBitmap *offscreen_draw_bitmap_1 = NULL; BBitmap *copy_bitmap = NULL; #ifdef USE_BE_CAIRO cairo_surface_t *cr_surface = NULL; cairo_t *cr_context = NULL; BLocker cr_surface_lock; #endif BPoint tt_absl_pos; BMessage *wait_for_release_message = NULL; color_space cspace; EmacsView () : BView (BRect (0, 0, 0, 0), "Emacs", B_FOLLOW_NONE, B_WILL_DRAW) { } ~EmacsView () { if (wait_for_release_message) gui_abort ("Wait for release message still exists"); TearDownDoubleBuffering (); if (!grab_view_locker.Lock ()) gui_abort ("Couldn't lock grab view locker"); if (grab_view == this) grab_view = NULL; grab_view_locker.Unlock (); } void AttachedToWindow (void) { cspace = B_RGBA32; } void MessageReceived (BMessage *msg) { uint32 buttons; BLooper *looper = Looper (); if (msg->what == WAIT_FOR_RELEASE) { if (wait_for_release_message) gui_abort ("Wait for release message already exists"); GetMouse (NULL, &buttons, false); if (!buttons) msg->SendReply (msg); else wait_for_release_message = looper->DetachCurrentMessage (); } else BView::MessageReceived (msg); } #ifdef USE_BE_CAIRO void DetachCairoSurface (void) { if (!cr_surface_lock.Lock ()) gui_abort ("Could not lock cr surface during detachment"); if (!cr_surface) gui_abort ("Trying to detach window cr surface when none exists"); cairo_destroy (cr_context); cairo_surface_destroy (cr_surface); cr_surface = NULL; cr_context = NULL; cr_surface_lock.Unlock (); } void AttachCairoSurface (void) { if (!cr_surface_lock.Lock ()) gui_abort ("Could not lock cr surface during attachment"); if (cr_surface) gui_abort ("Trying to attach cr surface when one already exists"); BRect bounds = offscreen_draw_bitmap_1->Bounds (); cr_surface = cairo_image_surface_create_for_data ((unsigned char *) offscreen_draw_bitmap_1->Bits (), CAIRO_FORMAT_ARGB32, BE_RECT_WIDTH (bounds), BE_RECT_HEIGHT (bounds), offscreen_draw_bitmap_1->BytesPerRow ()); if (!cr_surface) gui_abort ("Cr surface allocation failed for double-buffered view"); cr_context = cairo_create (cr_surface); if (!cr_context) gui_abort ("cairo_t allocation failed for double-buffered view"); cr_surface_lock.Unlock (); } #endif void TearDownDoubleBuffering (void) { if (offscreen_draw_view) { if (Window ()) ClearViewBitmap (); if (copy_bitmap) { delete copy_bitmap; copy_bitmap = NULL; } if (!looper_locked_count) if (!offscreen_draw_view->LockLooper ()) gui_abort ("Failed to lock offscreen draw view"); #ifdef USE_BE_CAIRO if (cr_surface) DetachCairoSurface (); #endif offscreen_draw_view->RemoveSelf (); delete offscreen_draw_view; offscreen_draw_view = NULL; delete offscreen_draw_bitmap_1; offscreen_draw_bitmap_1 = NULL; } } void AfterResize (void) { if (offscreen_draw_view) { if (!LockLooper ()) gui_abort ("Failed to lock looper after resize"); if (!offscreen_draw_view->LockLooper ()) gui_abort ("Failed to lock offscreen draw view after resize"); #ifdef USE_BE_CAIRO DetachCairoSurface (); #endif offscreen_draw_view->RemoveSelf (); delete offscreen_draw_bitmap_1; offscreen_draw_bitmap_1 = new BBitmap (Frame (), cspace, 1); if (offscreen_draw_bitmap_1->InitCheck () != B_OK) gui_abort ("Offscreen draw bitmap initialization failed"); BRect frame = Frame (); offscreen_draw_view->MoveTo (frame.left, frame.top); offscreen_draw_view->ResizeTo (BE_RECT_WIDTH (frame), BE_RECT_HEIGHT (frame)); offscreen_draw_bitmap_1->AddChild (offscreen_draw_view); #ifdef USE_BE_CAIRO AttachCairoSurface (); #endif if (looper_locked_count) { offscreen_draw_bitmap_1->Lock (); } UnlockLooper (); } } void Draw (BRect expose_bounds) { struct haiku_expose_event rq; EmacsWindow *w = (EmacsWindow *) Window (); if (w->shown_flag && offscreen_draw_view) { PushState (); SetDrawingMode (B_OP_ERASE); FillRect (Frame ()); PopState (); return; } if (!offscreen_draw_view) { if (sb_region.Contains (std::lrint (expose_bounds.left), std::lrint (expose_bounds.top)) && sb_region.Contains (std::lrint (expose_bounds.right), std::lrint (expose_bounds.top)) && sb_region.Contains (std::lrint (expose_bounds.left), std::lrint (expose_bounds.bottom)) && sb_region.Contains (std::lrint (expose_bounds.right), std::lrint (expose_bounds.bottom))) return; rq.x = std::floor (expose_bounds.left); rq.y = std::floor (expose_bounds.top); rq.width = std::ceil (expose_bounds.right - expose_bounds.left + 1); rq.height = std::ceil (expose_bounds.bottom - expose_bounds.top + 1); if (!rq.width) rq.width = 1; if (!rq.height) rq.height = 1; rq.window = this->Window (); haiku_write (FRAME_EXPOSED, &rq); } } void FlipBuffers (void) { if (!LockLooper ()) gui_abort ("Failed to lock looper during buffer flip"); if (!offscreen_draw_view) gui_abort ("Failed to lock offscreen view during buffer flip"); offscreen_draw_view->Sync (); EmacsWindow *w = (EmacsWindow *) Window (); w->shown_flag = 0; if (copy_bitmap && copy_bitmap->Bounds () != offscreen_draw_bitmap_1->Bounds ()) { delete copy_bitmap; copy_bitmap = NULL; } if (!copy_bitmap) copy_bitmap = new BBitmap (offscreen_draw_bitmap_1); else copy_bitmap->ImportBits (offscreen_draw_bitmap_1); if (copy_bitmap->InitCheck () != B_OK) gui_abort ("Failed to init copy bitmap during buffer flip"); SetViewBitmap (copy_bitmap, Frame (), Frame (), B_FOLLOW_NONE, 0); Invalidate (&invalid_region); invalid_region.MakeEmpty (); UnlockLooper (); return; } void SetUpDoubleBuffering (void) { if (!LockLooper ()) gui_abort ("Failed to lock self setting up double buffering"); if (offscreen_draw_view) gui_abort ("Failed to lock offscreen view setting up double buffering"); offscreen_draw_bitmap_1 = new BBitmap (Frame (), cspace, 1); if (offscreen_draw_bitmap_1->InitCheck () != B_OK) gui_abort ("Failed to init offscreen bitmap"); #ifdef USE_BE_CAIRO AttachCairoSurface (); #endif offscreen_draw_view = new BView (Frame (), NULL, B_FOLLOW_NONE, B_WILL_DRAW); offscreen_draw_bitmap_1->AddChild (offscreen_draw_view); if (looper_locked_count) { if (!offscreen_draw_bitmap_1->Lock ()) gui_abort ("Failed to lock bitmap after double buffering was set up"); } invalid_region.MakeEmpty (); UnlockLooper (); Invalidate (); } void MouseMoved (BPoint point, uint32 transit, const BMessage *msg) { struct haiku_mouse_motion_event rq; rq.just_exited_p = transit == B_EXITED_VIEW; rq.x = point.x; rq.y = point.y; rq.window = this->Window (); rq.time = system_time (); if (ToolTip ()) ToolTip ()->SetMouseRelativeLocation (BPoint (-(point.x - tt_absl_pos.x), -(point.y - tt_absl_pos.y))); if (!grab_view_locker.Lock ()) gui_abort ("Couldn't lock grab view locker"); if (grab_view && this != grab_view) { grab_view_locker.Unlock (); return; } grab_view_locker.Unlock (); if (movement_locker.Lock ()) { haiku_write (MOUSE_MOTION, &rq); movement_locker.Unlock (); } } void MouseDown (BPoint point) { struct haiku_button_event rq; uint32 buttons; this->GetMouse (&point, &buttons, false); if (!grab_view_locker.Lock ()) gui_abort ("Couldn't lock grab view locker"); if (buttons) grab_view = this; grab_view_locker.Unlock (); rq.window = this->Window (); if (!(previous_buttons & B_PRIMARY_MOUSE_BUTTON) && (buttons & B_PRIMARY_MOUSE_BUTTON)) rq.btn_no = 0; else if (!(previous_buttons & B_SECONDARY_MOUSE_BUTTON) && (buttons & B_SECONDARY_MOUSE_BUTTON)) rq.btn_no = 2; else if (!(previous_buttons & B_TERTIARY_MOUSE_BUTTON) && (buttons & B_TERTIARY_MOUSE_BUTTON)) rq.btn_no = 1; else return; previous_buttons = buttons; rq.x = point.x; rq.y = point.y; uint32_t mods = modifiers (); rq.modifiers = 0; if (mods & B_SHIFT_KEY) rq.modifiers |= HAIKU_MODIFIER_SHIFT; if (mods & B_CONTROL_KEY) rq.modifiers |= HAIKU_MODIFIER_CTRL; if (mods & B_COMMAND_KEY) rq.modifiers |= HAIKU_MODIFIER_ALT; if (mods & B_OPTION_KEY) rq.modifiers |= HAIKU_MODIFIER_SUPER; SetMouseEventMask (B_POINTER_EVENTS, (B_LOCK_WINDOW_FOCUS | B_NO_POINTER_HISTORY)); rq.time = system_time (); haiku_write (BUTTON_DOWN, &rq); } void MouseUp (BPoint point) { struct haiku_button_event rq; uint32 buttons; this->GetMouse (&point, &buttons, false); if (!grab_view_locker.Lock ()) gui_abort ("Couldn't lock grab view locker"); if (!buttons) grab_view = NULL; grab_view_locker.Unlock (); if (!buttons && wait_for_release_message) { wait_for_release_message->SendReply (wait_for_release_message); delete wait_for_release_message; wait_for_release_message = NULL; previous_buttons = buttons; return; } rq.window = this->Window (); if ((previous_buttons & B_PRIMARY_MOUSE_BUTTON) && !(buttons & B_PRIMARY_MOUSE_BUTTON)) rq.btn_no = 0; else if ((previous_buttons & B_SECONDARY_MOUSE_BUTTON) && !(buttons & B_SECONDARY_MOUSE_BUTTON)) rq.btn_no = 2; else if ((previous_buttons & B_TERTIARY_MOUSE_BUTTON) && !(buttons & B_TERTIARY_MOUSE_BUTTON)) rq.btn_no = 1; else return; previous_buttons = buttons; rq.x = point.x; rq.y = point.y; uint32_t mods = modifiers (); rq.modifiers = 0; if (mods & B_SHIFT_KEY) rq.modifiers |= HAIKU_MODIFIER_SHIFT; if (mods & B_CONTROL_KEY) rq.modifiers |= HAIKU_MODIFIER_CTRL; if (mods & B_COMMAND_KEY) rq.modifiers |= HAIKU_MODIFIER_ALT; if (mods & B_OPTION_KEY) rq.modifiers |= HAIKU_MODIFIER_SUPER; if (!buttons) SetMouseEventMask (0, 0); rq.time = system_time (); haiku_write (BUTTON_UP, &rq); } }; class EmacsScrollBar : public BScrollBar { public: int dragging = 0; bool horizontal; enum haiku_scroll_bar_part current_part; float old_value; scroll_bar_info info; /* True if button events should be passed to the parent. */ bool handle_button = false; bool in_overscroll = false; bool can_overscroll = false; bool maybe_overscroll = false; BPoint last_overscroll; int last_reported_overscroll_value; int max_value, real_max_value; int overscroll_start_value; EmacsScrollBar (int x, int y, int x1, int y1, bool horizontal_p) : BScrollBar (BRect (x, y, x1, y1), NULL, NULL, 0, 0, horizontal_p ? B_HORIZONTAL : B_VERTICAL) { BView *vw = (BView *) this; vw->SetResizingMode (B_FOLLOW_NONE); horizontal = horizontal_p; get_scroll_bar_info (&info); SetSteps (5000, 10000); } void MessageReceived (BMessage *msg) { int32 portion, range, dragging, value; float proportion; if (msg->what == SCROLL_BAR_UPDATE) { portion = msg->GetInt32 ("emacs:portion", 0); range = msg->GetInt32 ("emacs:range", 0); dragging = msg->GetInt32 ("emacs:dragging", 0); proportion = ((range <= 0 || portion <= 0) ? 1.0f : (float) portion / range); value = msg->GetInt32 ("emacs:units", 0); can_overscroll = msg->GetBool ("emacs:overscroll", false); if (value < 0) value = 0; if (dragging != 1) { if (in_overscroll || dragging != -1) { /* Set the value to the smallest possible one. Otherwise, the call to SetRange could lead to spurious updates. */ old_value = 0; SetValue (0); /* Unlike on Motif, PORTION isn't included in the total range of the scroll bar. */ SetRange (0, range - portion); SetProportion (proportion); max_value = range - portion; real_max_value = range; if (in_overscroll || value > max_value) value = max_value; old_value = roundf (value); SetValue (old_value); } else { value = Value (); old_value = 0; SetValue (0); SetRange (0, range - portion); SetProportion (proportion); old_value = value; SetValue (value); max_value = range - portion; real_max_value = range; } } } BScrollBar::MessageReceived (msg); } void ValueChanged (float new_value) { struct haiku_scroll_bar_value_event rq; struct haiku_scroll_bar_part_event part; new_value = Value (); if (dragging) { if (new_value != old_value) { if (dragging > 1) { SetValue (old_value); part.scroll_bar = this; part.window = Window (); part.part = current_part; haiku_write (SCROLL_BAR_PART_EVENT, &part); } else dragging++; } return; } if (new_value != old_value) { rq.scroll_bar = this; rq.window = Window (); rq.position = new_value; old_value = new_value; haiku_write (SCROLL_BAR_VALUE_EVENT, &rq); } } BRegion ButtonRegionFor (enum haiku_scroll_bar_part button) { BRegion region; BRect bounds; BRect rect; float button_size; bounds = Bounds (); bounds.InsetBy (0.0, 0.0); if (horizontal) button_size = bounds.Height () + 1.0f; else button_size = bounds.Width () + 1.0f; rect = BRect (bounds.left, bounds.top, bounds.left + button_size - 1.0f, bounds.top + button_size - 1.0f); if (button == HAIKU_SCROLL_BAR_UP_BUTTON) { if (!horizontal) { region.Include (rect); if (info.double_arrows) region.Include (rect.OffsetToCopy (bounds.left, bounds.bottom - 2 * button_size + 1)); } else { region.Include (rect); if (info.double_arrows) region.Include (rect.OffsetToCopy (bounds.right - 2 * button_size, bounds.top)); } } else { if (!horizontal) { region.Include (rect.OffsetToCopy (bounds.left, bounds.bottom - button_size)); if (info.double_arrows) region.Include (rect.OffsetByCopy (0.0, button_size)); } else { region.Include (rect.OffsetToCopy (bounds.right - button_size, bounds.top)); if (info.double_arrows) region.Include (rect.OffsetByCopy (button_size, 0.0)); } } return region; } void MouseDown (BPoint pt) { struct haiku_scroll_bar_drag_event rq; struct haiku_scroll_bar_part_event part; BRegion r; BLooper *looper; BMessage *message; int32 buttons, mods; BView *parent; looper = Looper (); message = NULL; if (!looper) GetMouse (&pt, (uint32 *) &buttons, false); else { message = looper->CurrentMessage (); if (!message || message->FindInt32 ("buttons", &buttons) != B_OK) GetMouse (&pt, (uint32 *) &buttons, false); } if (message && (message->FindInt32 ("modifiers", &mods) == B_OK) && mods & B_CONTROL_KEY) { /* Allow C-mouse-3 to split the window on a scroll bar. */ handle_button = true; parent = Parent (); parent->MouseDown (ConvertToParent (pt)); return; } if (buttons == B_PRIMARY_MOUSE_BUTTON) { r = ButtonRegionFor (HAIKU_SCROLL_BAR_UP_BUTTON); if (r.Contains (pt)) { part.scroll_bar = this; part.window = Window (); part.part = HAIKU_SCROLL_BAR_UP_BUTTON; dragging = 1; current_part = HAIKU_SCROLL_BAR_UP_BUTTON; haiku_write (SCROLL_BAR_PART_EVENT, &part); goto out; } r = ButtonRegionFor (HAIKU_SCROLL_BAR_DOWN_BUTTON); if (r.Contains (pt)) { part.scroll_bar = this; part.window = Window (); part.part = HAIKU_SCROLL_BAR_DOWN_BUTTON; dragging = 1; current_part = HAIKU_SCROLL_BAR_DOWN_BUTTON; haiku_write (SCROLL_BAR_PART_EVENT, &part); goto out; } maybe_overscroll = true; } rq.dragging_p = 1; rq.window = Window (); rq.scroll_bar = this; SetMouseEventMask (B_POINTER_EVENTS, (B_SUSPEND_VIEW_FOCUS | B_LOCK_WINDOW_FOCUS)); haiku_write (SCROLL_BAR_DRAG_EVENT, &rq); out: BScrollBar::MouseDown (pt); } void MouseUp (BPoint pt) { struct haiku_scroll_bar_drag_event rq; BView *parent; in_overscroll = false; maybe_overscroll = false; if (handle_button) { handle_button = false; parent = Parent (); parent->MouseUp (ConvertToParent (pt)); return; } rq.dragging_p = 0; rq.scroll_bar = this; rq.window = Window (); haiku_write (SCROLL_BAR_DRAG_EVENT, &rq); dragging = false; BScrollBar::MouseUp (pt); } void MouseMoved (BPoint point, uint32 transit, const BMessage *msg) { struct haiku_menu_bar_left_event rq; struct haiku_scroll_bar_value_event value_event; int range, diff, value, trough_size; BRect bounds; BPoint conv; uint32 buttons; GetMouse (NULL, &buttons, false); if (transit == B_EXITED_VIEW) { conv = ConvertToParent (point); rq.x = std::lrint (conv.x); rq.y = std::lrint (conv.y); rq.window = this->Window (); if (movement_locker.Lock ()) { haiku_write (MENU_BAR_LEFT, &rq); movement_locker.Unlock (); } } if (in_overscroll) { if (horizontal) diff = point.x - last_overscroll.x; else diff = point.y - last_overscroll.y; if (diff < 0) { in_overscroll = false; goto allow; } range = real_max_value; bounds = Bounds (); bounds.InsetBy (1.0, 1.0); value = overscroll_start_value; trough_size = (horizontal ? BE_RECT_WIDTH (bounds) : BE_RECT_HEIGHT (bounds)); trough_size -= (horizontal ? BE_RECT_HEIGHT (bounds) : BE_RECT_WIDTH (bounds)) / 2; if (info.double_arrows) trough_size -= (horizontal ? BE_RECT_HEIGHT (bounds) : BE_RECT_WIDTH (bounds)) / 2; value += ((double) range / trough_size) * diff; if (value != last_reported_overscroll_value) { last_reported_overscroll_value = value; value_event.scroll_bar = this; value_event.window = Window (); value_event.position = value; haiku_write (SCROLL_BAR_VALUE_EVENT, &value_event); return; } } else if (can_overscroll && (buttons == B_PRIMARY_MOUSE_BUTTON) && maybe_overscroll) { value = Value (); if (value >= max_value) { BScrollBar::MouseMoved (point, transit, msg); if (value == Value ()) { overscroll_start_value = value; in_overscroll = true; last_overscroll = point; last_reported_overscroll_value = value; MouseMoved (point, transit, msg); return; } } } allow: BScrollBar::MouseMoved (point, transit, msg); } }; class EmacsTitleMenuItem : public BMenuItem { public: EmacsTitleMenuItem (const char *str) : BMenuItem (str, NULL) { SetEnabled (0); } void DrawContent (void) { BMenu *menu = Menu (); menu->PushState (); menu->SetFont (be_bold_font); BView_SetHighColorForVisibleBell (menu, 0); BMenuItem::DrawContent (); menu->PopState (); } }; class EmacsMenuItem : public BMenuItem { public: int menu_bar_id = -1; void *menu_ptr = NULL; void *wind_ptr = NULL; char *key = NULL; char *help = NULL; EmacsMenuItem (const char *ky, const char *str, const char *help, BMessage *message = NULL) : BMenuItem (str, message) { if (ky) { key = strdup (ky); if (!key) gui_abort ("strdup failed"); } if (help) { this->help = strdup (help); if (!this->help) gui_abort ("strdup failed"); } } ~EmacsMenuItem () { if (key) free (key); if (help) free (help); } void DrawContent (void) { BMenu *menu = Menu (); BMenuItem::DrawContent (); if (key) { BRect r = Frame (); int w; menu->PushState (); menu->ClipToRect (r); menu->SetFont (be_plain_font); w = menu->StringWidth (key); menu->MovePenTo (BPoint (BE_RECT_WIDTH (r) - w - 4, menu->PenLocation ().y)); menu->DrawString (key); menu->PopState (); } } void GetContentSize (float *w, float *h) { BMenuItem::GetContentSize (w, h); if (Menu () && key) *w += 4 + Menu ()->StringWidth (key); } void Highlight (bool highlight_p) { struct haiku_menu_bar_help_event rq; struct haiku_dummy_event dummy; BMenu *menu = Menu (); BRect r; BPoint pt; uint32 buttons; if (help) menu->SetToolTip (highlight_p ? help : NULL); else { rq.window = wind_ptr; rq.mb_idx = highlight_p ? menu_bar_id : -1; rq.highlight_p = highlight_p; rq.data = menu_ptr; r = Frame (); menu->GetMouse (&pt, &buttons); if (!highlight_p || r.Contains (pt)) { if (menu_bar_id > 0) haiku_write (MENU_BAR_HELP_EVENT, &rq); else { haiku_write_without_signal (MENU_BAR_HELP_EVENT, &rq, true); haiku_write (DUMMY_EVENT, &dummy); } } } BMenuItem::Highlight (highlight_p); } }; class EmacsPopUpMenu : public BPopUpMenu { public: EmacsPopUpMenu (const char *name) : BPopUpMenu (name, 0) { } void FrameResized (float w, float h) { Invalidate (); BPopUpMenu::FrameResized (w, h); } }; static int32 start_running_application (void *data) { haiku_io_init_in_app_thread (); if (!((Emacs *) data)->Lock ()) gui_abort ("Failed to lock application"); ((Emacs *) data)->Run (); ((Emacs *) data)->Unlock (); return 0; } /* Take BITMAP, a reference to a BBitmap, and return a pointer to its data. */ void * BBitmap_data (void *bitmap) { return ((BBitmap *) bitmap)->Bits (); } /* Convert bitmap if required, placing the new bitmap in NEW_BITMAP, and return non-null if bitmap was successfully converted. Bitmaps should be freed with `BBitmap_free'. */ int BBitmap_convert (void *_bitmap, void **new_bitmap) { BBitmap *bitmap = (BBitmap *) _bitmap; if (bitmap->ColorSpace () == B_RGBA32) return -1; BRect bounds = bitmap->Bounds (); BBitmap *bmp = new (std::nothrow) BBitmap (bounds, B_RGBA32); if (!bmp || bmp->InitCheck () != B_OK) { if (bmp) delete bmp; return 0; } if (bmp->ImportBits (bitmap) != B_OK) { delete bmp; return 0; } *(BBitmap **) new_bitmap = bmp; return 1; } void BBitmap_free (void *bitmap) { delete (BBitmap *) bitmap; } /* Create new bitmap in RGB32 format, or in GRAY1 if MONO_P is non-zero. */ void * BBitmap_new (int width, int height, int mono_p) { BBitmap *bn = new (std::nothrow) BBitmap (BRect (0, 0, width - 1, height - 1), mono_p ? B_GRAY1 : B_RGB32); return bn->InitCheck () == B_OK ? (void *) bn : (void *) (delete bn, NULL); } void BBitmap_dimensions (void *bitmap, int *left, int *top, int *right, int *bottom, int32_t *bytes_per_row, int *mono_p) { BRect rect = ((BBitmap *) bitmap)->Bounds (); *left = rect.left; *top = rect.top; *right = rect.right; *bottom = rect.bottom; *bytes_per_row = ((BBitmap *) bitmap)->BytesPerRow (); *mono_p = (((BBitmap *) bitmap)->ColorSpace () == B_GRAY1); } /* Set up an application and return it. If starting the application thread fails, abort Emacs. */ void * BApplication_setup (void) { if (be_app) return be_app; thread_id id; Emacs *app; app = new Emacs; app->Unlock (); if ((id = spawn_thread (start_running_application, "Emacs app thread", B_DEFAULT_MEDIA_PRIORITY, app)) < 0) gui_abort ("spawn_thread failed"); resume_thread (id); app_thread = id; return app; } /* Set up and return a window with its view put in VIEW. */ void * BWindow_new (void *_view) { BWindow *window = new (std::nothrow) EmacsWindow; BView **v = (BView **) _view; if (!window) { *v = NULL; return window; } BView *vw = new (std::nothrow) EmacsView; if (!vw) { *v = NULL; window->LockLooper (); window->Quit (); return NULL; } /* Windows are created locked by the current thread, but calling Show for the first time causes them to be unlocked. To avoid a deadlock when a frame is created invisible in one thread, and another thread later tries to lock it, the window is unlocked here, and EmacsShow will lock it manually if it's being shown for the first time. */ window->UnlockLooper (); window->AddChild (vw); *v = vw; return window; } void BWindow_quit (void *window) { ((BWindow *) window)->LockLooper (); ((BWindow *) window)->Quit (); } /* Set WINDOW's offset to X, Y. */ void BWindow_set_offset (void *window, int x, int y) { BWindow *wn = (BWindow *) window; EmacsWindow *w = dynamic_cast (wn); if (w) { if (!w->LockLooper ()) gui_abort ("Failed to lock window looper setting offset"); w->EmacsMoveTo (x, y); w->UnlockLooper (); } else wn->MoveTo (x, y); } void BWindow_dimensions (void *window, int *width, int *height) { BWindow *w = (BWindow *) window; BRect frame = w->Frame (); *width = BE_RECT_WIDTH (frame); *height = BE_RECT_HEIGHT (frame); } /* Iconify WINDOW. */ void BWindow_iconify (void *window) { if (((BWindow *) window)->IsHidden ()) BWindow_set_visible (window, true); ((BWindow *) window)->Minimize (true); } /* Show or hide WINDOW. */ void BWindow_set_visible (void *window, int visible_p) { EmacsWindow *win = (EmacsWindow *) window; if (visible_p) { if (win->IsMinimized ()) win->Minimize (false); win->EmacsShow (); } else if (!win->IsHidden ()) { if (win->IsMinimized ()) win->Minimize (false); win->EmacsHide (); } } /* Change the title of WINDOW to the multibyte string TITLE. */ void BWindow_retitle (void *window, const char *title) { ((BWindow *) window)->SetTitle (title); } /* Resize WINDOW to WIDTH by HEIGHT. */ void BWindow_resize (void *window, int width, int height) { ((BWindow *) window)->ResizeTo (width - 1, height - 1); } /* Activate WINDOW, making it the subject of keyboard focus and bringing it to the front of the screen. */ void BWindow_activate (void *window) { ((BWindow *) window)->Activate (); } /* Return the pixel dimensions of the main screen in WIDTH and HEIGHT. */ void BScreen_px_dim (int *width, int *height) { BScreen screen; if (!screen.IsValid ()) gui_abort ("Invalid screen"); BRect frame = screen.Frame (); *width = frame.right - frame.left; *height = frame.bottom - frame.top; } /* Resize VIEW to WIDTH, HEIGHT. */ void BView_resize_to (void *view, int width, int height) { EmacsView *vw = (EmacsView *) view; if (!vw->LockLooper ()) gui_abort ("Failed to lock view for resize"); vw->ResizeTo (width, height); vw->AfterResize (); vw->UnlockLooper (); } void * BCursor_create_default (void) { return new BCursor (B_CURSOR_ID_SYSTEM_DEFAULT); } void * BCursor_create_modeline (void) { return new BCursor (B_CURSOR_ID_CONTEXT_MENU); } void * BCursor_from_id (enum haiku_cursor cursor) { return new BCursor ((enum BCursorID) cursor); } void * BCursor_create_i_beam (void) { return new BCursor (B_CURSOR_ID_I_BEAM); } void * BCursor_create_progress_cursor (void) { return new BCursor (B_CURSOR_ID_PROGRESS); } void * BCursor_create_grab (void) { return new BCursor (B_CURSOR_ID_GRAB); } void BCursor_delete (void *cursor) { if (cursor) delete (BCursor *) cursor; } void BView_set_view_cursor (void *view, void *cursor) { if (!((BView *) view)->LockLooper ()) gui_abort ("Failed to lock view setting cursor"); ((BView *) view)->SetViewCursor ((BCursor *) cursor); ((BView *) view)->UnlockLooper (); } void BWindow_Flush (void *window) { ((BWindow *) window)->Flush (); } /* Make a scrollbar, attach it to VIEW's window, and return it. */ void * BScrollBar_make_for_view (void *view, int horizontal_p, int x, int y, int x1, int y1, void *scroll_bar_ptr) { EmacsScrollBar *sb = new EmacsScrollBar (x, y, x1, y1, horizontal_p); BView *vw = (BView *) view; BView *sv = (BView *) sb; if (!vw->LockLooper ()) gui_abort ("Failed to lock scrollbar owner"); vw->AddChild ((BView *) sb); sv->WindowActivated (vw->Window ()->IsActive ()); vw->UnlockLooper (); return sb; } void BScrollBar_delete (void *sb) { BView *view = (BView *) sb; BView *pr = view->Parent (); if (!pr->LockLooper ()) gui_abort ("Failed to lock scrollbar parent"); pr->RemoveChild (view); pr->UnlockLooper (); delete (EmacsScrollBar *) sb; } void BView_move_frame (void *view, int x, int y, int x1, int y1) { BView *vw = (BView *) view; if (!vw->LockLooper ()) gui_abort ("Failed to lock view moving frame"); vw->MoveTo (x, y); vw->ResizeTo (x1 - x, y1 - y); vw->UnlockLooper (); } /* DRAGGING can either be 0 (which means to update everything), 1 (which means to update nothing), or -1 (which means to update only the thumb size and range). */ void BView_scroll_bar_update (void *sb, int portion, int whole, int position, int dragging, bool can_overscroll) { BScrollBar *bar = (BScrollBar *) sb; BMessage msg = BMessage (SCROLL_BAR_UPDATE); BMessenger mr = BMessenger (bar); msg.AddInt32 ("emacs:range", whole); msg.AddInt32 ("emacs:units", position); msg.AddInt32 ("emacs:portion", portion); msg.AddInt32 ("emacs:dragging", dragging); msg.AddBool ("emacs:overscroll", can_overscroll); mr.SendMessage (&msg); } /* Return the default scrollbar size. */ int BScrollBar_default_size (int horizontal_p) { return be_control_look->GetScrollBarWidth (horizontal_p ? B_HORIZONTAL : B_VERTICAL); } /* Invalidate VIEW, causing it to be drawn again. */ void BView_invalidate (void *view) { BView *vw = (BView *) view; if (!vw->LockLooper ()) gui_abort ("Couldn't lock view while invalidating it"); vw->Invalidate (); vw->UnlockLooper (); } /* Lock VIEW in preparation for drawing operations. This should be called before any attempt to draw onto VIEW or to lock it for Cairo drawing. `BView_draw_unlock' should be called afterwards. If any drawing is going to take place, INVALID_REGION should be true, and X, Y, WIDTH, HEIGHT should specify a rectangle in which the drawing will take place. */ void BView_draw_lock (void *view, bool invalidate_region, int x, int y, int width, int height) { EmacsView *vw = (EmacsView *) view; if (vw->looper_locked_count) { vw->looper_locked_count++; if (invalidate_region && vw->offscreen_draw_view) vw->invalid_region.Include (BRect (x, y, x + width - 1, y + height - 1)); return; } BView *v = (BView *) find_appropriate_view_for_draw (vw); if (v != vw) { if (!vw->offscreen_draw_bitmap_1->Lock ()) gui_abort ("Failed to lock offscreen bitmap while acquiring draw lock"); } else if (!v->LockLooper ()) gui_abort ("Failed to lock draw view while acquiring draw lock"); if (v != vw && !vw->LockLooper ()) gui_abort ("Failed to lock view while acquiring draw lock"); if (invalidate_region && vw->offscreen_draw_view) vw->invalid_region.Include (BRect (x, y, x + width - 1, y + height - 1)); vw->looper_locked_count++; } void BView_invalidate_region (void *view, int x, int y, int width, int height) { EmacsView *vw = (EmacsView *) view; if (vw->offscreen_draw_view) vw->invalid_region.Include (BRect (x, y, x + width - 1, y + height - 1)); } void BView_draw_unlock (void *view) { EmacsView *vw = (EmacsView *) view; if (--vw->looper_locked_count) return; BView *v = (BView *) find_appropriate_view_for_draw (view); if (v == vw) vw->UnlockLooper (); else { vw->offscreen_draw_bitmap_1->Unlock (); vw->UnlockLooper (); } } void BWindow_center_on_screen (void *window) { BWindow *w = (BWindow *) window; w->CenterOnScreen (); } /* Tell VIEW it has been clicked at X by Y. */ void BView_mouse_down (void *view, int x, int y) { BView *vw = (BView *) view; if (vw->LockLooper ()) { vw->MouseDown (BPoint (x, y)); vw->UnlockLooper (); } } /* Tell VIEW the mouse has been released at X by Y. */ void BView_mouse_up (void *view, int x, int y) { BView *vw = (BView *) view; if (vw->LockLooper ()) { vw->MouseUp (BPoint (x, y)); vw->UnlockLooper (); } } /* Tell VIEW that the mouse has moved to Y by Y. */ void BView_mouse_moved (void *view, int x, int y, uint32_t transit) { BView *vw = (BView *) view; if (vw->LockLooper ()) { vw->MouseMoved (BPoint (x, y), transit, NULL); vw->UnlockLooper (); } } /* Import fringe bitmap (short array, low bit rightmost) BITS into BITMAP using the B_GRAY1 colorspace. */ void BBitmap_import_fringe_bitmap (void *bitmap, unsigned short *bits, int wd, int h) { BBitmap *bmp = (BBitmap *) bitmap; unsigned char *data = (unsigned char *) bmp->Bits (); int i; for (i = 0; i < h; i++) { if (wd <= 8) data[0] = bits[i] & 0xff; else { data[1] = bits[i] & 0xff; data[0] = bits[i] >> 8; } data += bmp->BytesPerRow (); } } void BBitmap_import_mono_bits (void *bitmap, void *bits, int wd, int h) { BBitmap *bmp = (BBitmap *) bitmap; if (wd % 8) wd += 8 - (wd % 8); bmp->ImportBits (bits, wd / 8 * h, wd / 8, 0, B_GRAY1); } /* Make a scrollbar at X, Y known to the view VIEW. */ void BView_publish_scroll_bar (void *view, int x, int y, int width, int height) { EmacsView *vw = (EmacsView *) view; if (vw->LockLooper ()) { vw->sb_region.Include (BRect (x, y, x - 1 + width, y - 1 + height)); vw->UnlockLooper (); } } void BView_forget_scroll_bar (void *view, int x, int y, int width, int height) { EmacsView *vw = (EmacsView *) view; if (vw->LockLooper ()) { vw->sb_region.Exclude (BRect (x, y, x - 1 + width, y - 1 + height)); vw->UnlockLooper (); } } bool BView_inside_scroll_bar (void *view, int x, int y) { EmacsView *vw = (EmacsView *) view; bool val; if (vw->LockLooper ()) { val = vw->sb_region.Contains (BPoint (x, y)); vw->UnlockLooper (); } else val = false; return val; } void BView_get_mouse (void *view, int *x, int *y) { BPoint l; BView *vw = (BView *) view; if (!vw->LockLooper ()) gui_abort ("Failed to lock view in BView_get_mouse"); vw->GetMouse (&l, NULL, 1); vw->UnlockLooper (); *x = std::lrint (l.x); *y = std::lrint (l.y); } /* Perform an in-place conversion of X and Y from VIEW's coordinate system to its screen's coordinate system. */ void BView_convert_to_screen (void *view, int *x, int *y) { BPoint l = BPoint (*x, *y); BView *vw = (BView *) view; if (!vw->LockLooper ()) gui_abort ("Failed to lock view in convert_to_screen"); vw->ConvertToScreen (&l); vw->UnlockLooper (); *x = std::lrint (l.x); *y = std::lrint (l.y); } void BView_convert_from_screen (void *view, int *x, int *y) { BPoint l = BPoint (*x, *y); BView *vw = (BView *) view; if (!vw->LockLooper ()) gui_abort ("Failed to lock view in convert_from_screen"); vw->ConvertFromScreen (&l); vw->UnlockLooper (); *x = std::lrint (l.x); *y = std::lrint (l.y); } /* Decorate or undecorate WINDOW depending on DECORATE_P. */ void BWindow_change_decoration (void *window, int decorate_p) { EmacsWindow *w = (EmacsWindow *) window; if (!w->LockLooper ()) gui_abort ("Failed to lock window while changing its decorations"); if (!w->override_redirect_p) { if (decorate_p) w->SetLook (B_TITLED_WINDOW_LOOK); else w->SetLook (B_NO_BORDER_WINDOW_LOOK); } else { if (decorate_p) w->pre_override_redirect_look = B_TITLED_WINDOW_LOOK; else w->pre_override_redirect_look = B_NO_BORDER_WINDOW_LOOK; } w->UnlockLooper (); } /* Decorate WINDOW appropriately for use as a tooltip. */ void BWindow_set_tooltip_decoration (void *window) { BWindow *w = (BWindow *) window; if (!w->LockLooper ()) gui_abort ("Failed to lock window while setting ttip decoration"); w->SetLook (B_BORDERED_WINDOW_LOOK); w->SetFeel (kMenuWindowFeel); w->SetFlags (B_NOT_ZOOMABLE | B_NOT_MINIMIZABLE | B_AVOID_FRONT | B_AVOID_FOCUS); w->UnlockLooper (); } /* Set B_AVOID_FOCUS on WINDOW if AVOID_FOCUS_P is non-nil, or clear it otherwise. */ void BWindow_set_avoid_focus (void *window, int avoid_focus_p) { BWindow *w = (BWindow *) window; if (!w->LockLooper ()) gui_abort ("Failed to lock window while setting avoid focus"); if (!avoid_focus_p) w->SetFlags (w->Flags () & ~B_AVOID_FOCUS); else w->SetFlags (w->Flags () | B_AVOID_FOCUS); w->UnlockLooper (); } void BView_emacs_delete (void *view) { EmacsView *vw = (EmacsView *) view; if (!vw->LockLooper ()) gui_abort ("Failed to lock view while deleting it"); vw->RemoveSelf (); delete vw; } /* Return the current workspace. */ uint32_t haiku_current_workspace (void) { return current_workspace (); } /* Return a bitmask consisting of workspaces WINDOW is on. */ uint32_t BWindow_workspaces (void *window) { return ((BWindow *) window)->Workspaces (); } /* Create a popup menu. */ void * BPopUpMenu_new (const char *name) { BPopUpMenu *menu = new EmacsPopUpMenu (name); menu->SetRadioMode (0); return menu; } /* Add a title item to MENU. These items cannot be highlighted or triggered, and their labels will display as bold text. */ void BMenu_add_title (void *menu, const char *text) { EmacsTitleMenuItem *it = new EmacsTitleMenuItem (text); BMenu *mn = (BMenu *) menu; mn->AddItem (it); } /* Add an item to the menu MENU. */ void BMenu_add_item (void *menu, const char *label, void *ptr, bool enabled_p, bool marked_p, bool mbar_p, void *mbw_ptr, const char *key, const char *help) { BMenu *m = (BMenu *) menu; BMessage *msg; if (ptr) msg = new BMessage (); EmacsMenuItem *it = new EmacsMenuItem (key, label, help, ptr ? msg : NULL); it->SetTarget (m->Window ()); it->SetEnabled (enabled_p); it->SetMarked (marked_p); if (mbar_p) { it->menu_bar_id = (intptr_t) ptr; it->wind_ptr = mbw_ptr; } it->menu_ptr = ptr; if (ptr) msg->AddPointer ("menuptr", ptr); m->AddItem (it); } /* Add a separator to the menu MENU. */ void BMenu_add_separator (void *menu) { BMenu *m = (BMenu *) menu; m->AddSeparatorItem (); } /* Create a submenu and attach it to MENU. */ void * BMenu_new_submenu (void *menu, const char *label, bool enabled_p) { BMenu *m = (BMenu *) menu; BMenu *mn = new BMenu (label, B_ITEMS_IN_COLUMN); mn->SetRadioMode (0); BMenuItem *i = new BMenuItem (mn); i->SetEnabled (enabled_p); m->AddItem (i); return mn; } /* Create a submenu that notifies Emacs upon opening. */ void * BMenu_new_menu_bar_submenu (void *menu, const char *label) { BMenu *m = (BMenu *) menu; BMenu *mn = new BMenu (label, B_ITEMS_IN_COLUMN); mn->SetRadioMode (0); BMenuItem *i = new BMenuItem (mn); i->SetEnabled (1); m->AddItem (i); return mn; } /* Run MENU, waiting for it to close, and return a pointer to the data of the selected item (if one exists), or NULL. X, Y should be in the screen coordinate system. */ void * BMenu_run (void *menu, int x, int y, void (*run_help_callback) (void *, void *), void (*block_input_function) (void), void (*unblock_input_function) (void), struct timespec (*process_pending_signals_function) (void), void *run_help_callback_data) { BPopUpMenu *mn = (BPopUpMenu *) menu; enum haiku_event_type type; void *buf; void *ptr = NULL; struct be_popup_menu_data data; struct object_wait_info infos[3]; struct haiku_menu_bar_help_event *event; BMessage *msg; ssize_t stat; struct timespec next_time; bigtime_t timeout; block_input_function (); port_popup_menu_to_emacs = create_port (1800, "popup menu port"); data.x = x; data.y = y; data.menu = mn; unblock_input_function (); if (port_popup_menu_to_emacs < B_OK) return NULL; block_input_function (); mn->SetRadioMode (0); buf = alloca (200); infos[0].object = port_popup_menu_to_emacs; infos[0].type = B_OBJECT_TYPE_PORT; infos[0].events = B_EVENT_READ; infos[1].object = spawn_thread (be_popup_menu_thread_entry, "Menu tracker", B_DEFAULT_MEDIA_PRIORITY, (void *) &data); infos[1].type = B_OBJECT_TYPE_THREAD; infos[1].events = B_EVENT_INVALID; infos[2].object = port_application_to_emacs; infos[2].type = B_OBJECT_TYPE_PORT; infos[2].events = B_EVENT_READ; unblock_input_function (); if (infos[1].object < B_OK) { block_input_function (); delete_port (port_popup_menu_to_emacs); unblock_input_function (); return NULL; } block_input_function (); resume_thread (infos[1].object); unblock_input_function (); while (true) { next_time = process_pending_signals_function (); if (next_time.tv_nsec < 0) timeout = 10000000000; else timeout = (next_time.tv_sec * 1000000 + next_time.tv_nsec / 1000); if ((stat = wait_for_objects_etc ((object_wait_info *) &infos, 3, B_RELATIVE_TIMEOUT, timeout)) < B_OK) { if (stat == B_INTERRUPTED || stat == B_TIMED_OUT || stat == B_WOULD_BLOCK) continue; else gui_abort ("Failed to wait for popup"); } if (infos[0].events & B_EVENT_READ) { while (!haiku_read_with_timeout (&type, buf, 200, 0, true)) { switch (type) { case MENU_BAR_HELP_EVENT: event = (struct haiku_menu_bar_help_event *) buf; run_help_callback (event->highlight_p ? event->data : NULL, run_help_callback_data); break; default: gui_abort ("Unknown popup menu event"); } } } if (infos[1].events & B_EVENT_INVALID) { block_input_function (); msg = (BMessage *) popup_track_message; if (popup_track_message) ptr = (void *) msg->GetPointer ("menuptr"); delete_port (port_popup_menu_to_emacs); unblock_input_function (); return ptr; } infos[0].events = B_EVENT_READ; infos[1].events = B_EVENT_INVALID; infos[2].events = B_EVENT_READ; } } /* Delete the entire menu hierarchy of MENU, and then delete MENU itself. */ void BPopUpMenu_delete (void *menu) { delete (BPopUpMenu *) menu; } /* Create a menubar, attach it to VIEW, and return it. */ void * BMenuBar_new (void *view) { BView *vw = (BView *) view; EmacsMenuBar *bar = new EmacsMenuBar (); if (!vw->LockLooper ()) gui_abort ("Failed to lock menu bar parent"); vw->AddChild ((BView *) bar); vw->UnlockLooper (); return bar; } /* Delete MENUBAR along with all subitems. */ void BMenuBar_delete (void *menubar) { BView *vw = (BView *) menubar; BView *p = vw->Parent (); EmacsWindow *window = (EmacsWindow *) p->Window (); if (!p->LockLooper ()) gui_abort ("Failed to lock menu bar parent while removing menubar"); window->SetKeyMenuBar (NULL); /* MenusEnded isn't called if the menu bar is destroyed before it closes. */ window->menu_bar_active_p = false; vw->RemoveSelf (); p->UnlockLooper (); delete vw; } /* Delete all items from MENU. */ void BMenu_delete_all (void *menu) { BMenu *mn = (BMenu *) menu; mn->RemoveItems (0, mn->CountItems (), true); } /* Delete COUNT items from MENU starting from START. */ void BMenu_delete_from (void *menu, int start, int count) { BMenu *mn = (BMenu *) menu; mn->RemoveItems (start, count, true); } /* Count items in menu MENU. */ int BMenu_count_items (void *menu) { return ((BMenu *) menu)->CountItems (); } /* Find the item in MENU at IDX. */ void * BMenu_item_at (void *menu, int idx) { return ((BMenu *) menu)->ItemAt (idx); } /* Set ITEM's label to LABEL. */ void BMenu_item_set_label (void *item, const char *label) { ((BMenuItem *) item)->SetLabel (label); } /* Get ITEM's menu. */ void * BMenu_item_get_menu (void *item) { return ((BMenuItem *) item)->Submenu (); } /* Emit a beep noise. */ void haiku_ring_bell (void) { beep (); } /* Create a BAlert with TEXT. */ void * BAlert_new (const char *text, enum haiku_alert_type type) { return new BAlert (NULL, text, NULL, NULL, NULL, B_WIDTH_AS_USUAL, (enum alert_type) type); } /* Add a button to ALERT and return the button. */ void * BAlert_add_button (void *alert, const char *text) { BAlert *al = (BAlert *) alert; al->AddButton (text); return al->ButtonAt (al->CountButtons () - 1); } /* Make sure the leftmost button is grouped to the left hand side of the alert. */ void BAlert_set_offset_spacing (void *alert) { BAlert *al = (BAlert *) alert; al->SetButtonSpacing (B_OFFSET_SPACING); } static int32 be_alert_thread_entry (void *thread_data) { BAlert *alert = (BAlert *) thread_data; int32 value; if (alert->LockLooper ()) value = alert->Go (); else value = -1; alert_popup_value = value; return 0; } /* Run ALERT, returning the number of the button that was selected, or -1 if no button was selected before the alert was closed. */ int32 BAlert_go (void *alert, void (*block_input_function) (void), void (*unblock_input_function) (void), void (*process_pending_signals_function) (void)) { struct object_wait_info infos[2]; ssize_t stat; BAlert *alert_object = (BAlert *) alert; infos[0].object = port_application_to_emacs; infos[0].type = B_OBJECT_TYPE_PORT; infos[0].events = B_EVENT_READ; block_input_function (); /* Alerts are created locked, just like other windows. */ alert_object->UnlockLooper (); infos[1].object = spawn_thread (be_alert_thread_entry, "Popup tracker", B_DEFAULT_MEDIA_PRIORITY, alert); infos[1].type = B_OBJECT_TYPE_THREAD; infos[1].events = B_EVENT_INVALID; unblock_input_function (); if (infos[1].object < B_OK) return -1; block_input_function (); resume_thread (infos[1].object); unblock_input_function (); while (true) { stat = wait_for_objects ((object_wait_info *) &infos, 2); if (stat == B_INTERRUPTED) continue; else if (stat < B_OK) gui_abort ("Failed to wait for popup dialog"); if (infos[1].events & B_EVENT_INVALID) return alert_popup_value; if (infos[0].events & B_EVENT_READ) process_pending_signals_function (); infos[0].events = B_EVENT_READ; infos[1].events = B_EVENT_INVALID; } } /* Enable or disable BUTTON depending on ENABLED_P. */ void BButton_set_enabled (void *button, int enabled_p) { ((BButton *) button)->SetEnabled (enabled_p); } /* Set VIEW's tooltip to TOOLTIP. */ void BView_set_tooltip (void *view, const char *tooltip) { ((BView *) view)->SetToolTip (tooltip); } /* Set VIEW's tooltip to a sticky tooltip at X by Y. */ void BView_set_and_show_sticky_tooltip (void *view, const char *tooltip, int x, int y) { BToolTip *tip; BView *vw = (BView *) view; if (!vw->LockLooper ()) gui_abort ("Failed to lock view while showing sticky tooltip"); vw->SetToolTip (tooltip); tip = vw->ToolTip (); BPoint pt; EmacsView *ev = dynamic_cast (vw); if (ev) ev->tt_absl_pos = BPoint (x, y); vw->GetMouse (&pt, NULL, 1); pt.x -= x; pt.y -= y; pt.x = -pt.x; pt.y = -pt.y; tip->SetMouseRelativeLocation (pt); tip->SetSticky (1); vw->ShowToolTip (tip); vw->UnlockLooper (); } /* Delete ALERT. */ void BAlert_delete (void *alert) { delete (BAlert *) alert; } /* Place the resolution of the monitor in DPI in RSSX and RSSY. */ void BScreen_res (double *rrsx, double *rrsy) { BScreen s (B_MAIN_SCREEN_ID); if (!s.IsValid ()) gui_abort ("Invalid screen for resolution checks"); monitor_info i; if (s.GetMonitorInfo (&i) == B_OK) { *rrsx = (double) i.width / (double) 2.54; *rrsy = (double) i.height / (double) 2.54; } else { *rrsx = 72.27; *rrsy = 72.27; } } /* Add WINDOW to OTHER_WINDOW's subset and parent it to OTHER_WINDOW. */ void EmacsWindow_parent_to (void *window, void *other_window) { EmacsWindow *w = (EmacsWindow *) window; if (!w->LockLooper ()) gui_abort ("Failed to lock window while parenting"); w->ParentTo ((EmacsWindow *) other_window); w->UnlockLooper (); } void EmacsWindow_unparent (void *window) { EmacsWindow *w = (EmacsWindow *) window; if (!w->LockLooper ()) gui_abort ("Failed to lock window while unparenting"); w->UnparentAndUnlink (); w->UnlockLooper (); } /* Place text describing the current version of Haiku in VERSION, which should be a buffer LEN bytes wide. */ void be_get_version_string (char *version, int len) { std::strncpy (version, "Unknown Haiku release", len - 1); BPath path; if (find_directory (B_BEOS_LIB_DIRECTORY, &path) == B_OK) { path.Append ("libbe.so"); BAppFileInfo appFileInfo; version_info versionInfo; BFile file; if (file.SetTo (path.Path (), B_READ_ONLY) == B_OK && appFileInfo.SetTo (&file) == B_OK && appFileInfo.GetVersionInfo (&versionInfo, B_APP_VERSION_KIND) == B_OK && versionInfo.short_info[0] != '\0') std::strncpy (version, versionInfo.short_info, len - 1); } } /* Return the amount of color planes in the current display. */ int be_get_display_planes (void) { color_space space = dpy_color_space; if (space == B_NO_COLOR_SPACE) { BScreen screen; /* This is actually a very slow operation. */ if (!screen.IsValid ()) gui_abort ("Invalid screen"); space = dpy_color_space = screen.ColorSpace (); } if (space == B_RGB32 || space == B_RGB24) return 24; if (space == B_RGB16) return 16; if (space == B_RGB15) return 15; if (space == B_CMAP8) return 8; gui_abort ("Bad colorspace for screen"); /* https://www.haiku-os.org/docs/api/classBScreen.html says a valid screen can't be anything else. */ return -1; } /* Return the amount of colors the display can handle. */ int be_get_display_color_cells (void) { color_space space = dpy_color_space; if (space == B_NO_COLOR_SPACE) { BScreen screen; if (!screen.IsValid ()) gui_abort ("Invalid screen"); space = dpy_color_space = screen.ColorSpace (); } if (space == B_RGB32 || space == B_RGB24) return 1677216; if (space == B_RGB16) return 65536; if (space == B_RGB15) return 32768; if (space == B_CMAP8) return 256; gui_abort ("Bad colorspace for screen"); return -1; } /* Warp the pointer to X by Y. */ void be_warp_pointer (int x, int y) { /* We're not supposed to use the following function without a BWindowScreen object, but in Haiku nothing actually prevents us from doing so. */ set_mouse_position (x, y); } /* Update the position of CHILD in WINDOW without actually moving it. */ void EmacsWindow_move_weak_child (void *window, void *child, int xoff, int yoff) { EmacsWindow *w = (EmacsWindow *) window; EmacsWindow *c = (EmacsWindow *) child; if (!w->LockLooper ()) gui_abort ("Couldn't lock window for weak move"); w->MoveChild (c, xoff, yoff, 1); w->UnlockLooper (); } /* Find an appropriate view to draw onto. If VW is double-buffered, this will be the view used for double buffering instead of VW itself. */ void * find_appropriate_view_for_draw (void *vw) { BView *v = (BView *) vw; EmacsView *ev = dynamic_cast(v); if (!ev) return v; return ev->offscreen_draw_view ? ev->offscreen_draw_view : vw; } /* Set up double buffering for VW. */ void EmacsView_set_up_double_buffering (void *vw) { EmacsView *view = (EmacsView *) vw; if (!view->LockLooper ()) gui_abort ("Couldn't lock view while setting up double buffering"); if (view->offscreen_draw_view) { view->UnlockLooper (); return; } view->SetUpDoubleBuffering (); view->UnlockLooper (); } /* Flip and invalidate the view VW. */ void EmacsView_flip_and_blit (void *vw) { EmacsView *view = (EmacsView *) vw; if (!view->offscreen_draw_view) return; if (!view->LockLooper ()) gui_abort ("Couldn't lock view in flip_and_blit"); view->FlipBuffers (); view->UnlockLooper (); } /* Disable double buffering for VW. */ void EmacsView_disable_double_buffering (void *vw) { EmacsView *view = (EmacsView *) vw; if (!view->LockLooper ()) gui_abort ("Couldn't lock view tearing down double buffering"); view->TearDownDoubleBuffering (); view->UnlockLooper (); } /* Return non-0 if VW is double-buffered. */ int EmacsView_double_buffered_p (void *vw) { EmacsView *view = (EmacsView *) vw; if (!view->LockLooper ()) gui_abort ("Couldn't lock view testing double buffering status"); int db_p = !!view->offscreen_draw_view; view->UnlockLooper (); return db_p; } struct popup_file_dialog_data { BMessage *msg; BFilePanel *panel; BEntry *entry; }; static void unwind_popup_file_dialog (void *ptr) { struct popup_file_dialog_data *data = (struct popup_file_dialog_data *) ptr; BFilePanel *panel = data->panel; delete panel; delete data->entry; delete data->msg; } static void be_popup_file_dialog_safe_set_target (BFilePanel *dialog, BWindow *window) { dialog->SetTarget (BMessenger (window)); } /* Popup a file dialog. */ char * be_popup_file_dialog (int open_p, const char *default_dir, int must_match_p, int dir_only_p, void *window, const char *save_text, const char *prompt, void (*block_input_function) (void), void (*unblock_input_function) (void), void (*maybe_quit_function) (void)) { specpdl_ref idx = c_specpdl_idx_from_cxx (); /* setjmp/longjmp is UB with automatic objects. */ block_input_function (); BWindow *w = (BWindow *) window; uint32_t mode = dir_only_p ? B_DIRECTORY_NODE : B_FILE_NODE | B_DIRECTORY_NODE; BEntry *path = new BEntry; BMessage *msg = new BMessage ('FPSE'); BFilePanel *panel = new BFilePanel (open_p ? B_OPEN_PANEL : B_SAVE_PANEL, NULL, NULL, mode); struct popup_file_dialog_data dat; dat.entry = path; dat.msg = msg; dat.panel = panel; record_c_unwind_protect_from_cxx (unwind_popup_file_dialog, &dat); if (default_dir) { if (path->SetTo (default_dir, 0) != B_OK) default_dir = NULL; } panel->SetMessage (msg); if (default_dir) panel->SetPanelDirectory (path); if (save_text) panel->SetSaveText (save_text); panel->SetHideWhenDone (0); panel->Window ()->SetTitle (prompt); be_popup_file_dialog_safe_set_target (panel, w); panel->Show (); unblock_input_function (); void *buf = alloca (200); while (1) { enum haiku_event_type type; char *ptr = NULL; if (!haiku_read_with_timeout (&type, buf, 200, 1000000, false)) { block_input_function (); if (type != FILE_PANEL_EVENT) haiku_write (type, buf); else if (!ptr) ptr = (char *) ((struct haiku_file_panel_event *) buf)->ptr; unblock_input_function (); maybe_quit_function (); } ssize_t b_s; block_input_function (); haiku_read_size (&b_s, false); if (!b_s || ptr || panel->Window ()->IsHidden ()) { c_unbind_to_nil_from_cxx (idx); unblock_input_function (); return ptr; } unblock_input_function (); } } void be_app_quit (void) { if (be_app) { while (!be_app->Lock ()); be_app->Quit (); } } /* Zoom WINDOW. */ void BWindow_zoom (void *window) { BWindow *w = (BWindow *) window; w->Zoom (); } /* Make WINDOW fullscreen if FULLSCREEN_P. */ void EmacsWindow_make_fullscreen (void *window, int fullscreen_p) { EmacsWindow *w = (EmacsWindow *) window; w->MakeFullscreen (fullscreen_p); } /* Unzoom (maximize) WINDOW. */ void EmacsWindow_unzoom (void *window) { EmacsWindow *w = (EmacsWindow *) window; w->UnZoom (); } /* Move the pointer into MBAR and start tracking. */ void BMenuBar_start_tracking (void *mbar) { EmacsMenuBar *mb = (EmacsMenuBar *) mbar; if (!mb->LockLooper ()) gui_abort ("Couldn't lock menubar"); BRect frame = mb->Frame (); BPoint pt = frame.LeftTop (); BPoint l = pt; mb->Parent ()->ConvertToScreen (&pt); set_mouse_position (pt.x, pt.y); mb->MouseDown (l); mb->UnlockLooper (); } #ifdef HAVE_NATIVE_IMAGE_API int be_can_translate_type_to_bitmap_p (const char *mime) { BTranslatorRoster *r = BTranslatorRoster::Default (); translator_id *ids; int32 id_len; if (r->GetAllTranslators (&ids, &id_len) != B_OK) return 0; int found_in = 0; int found_out = 0; for (int i = 0; i < id_len; ++i) { found_in = 0; found_out = 0; const translation_format *i_fmts; const translation_format *o_fmts; int32 i_count, o_count; if (r->GetInputFormats (ids[i], &i_fmts, &i_count) != B_OK) continue; if (r->GetOutputFormats (ids[i], &o_fmts, &o_count) != B_OK) continue; for (int x = 0; x < i_count; ++x) { if (!strcmp (i_fmts[x].MIME, mime)) { found_in = 1; break; } } for (int x = 0; x < i_count; ++x) { if (!strcmp (o_fmts[x].MIME, "image/x-be-bitmap") || !strcmp (o_fmts[x].MIME, "image/x-vnd.Be-bitmap")) { found_out = 1; break; } } if (found_in && found_out) break; } delete [] ids; return found_in && found_out; } void * be_translate_bitmap_from_file_name (const char *filename) { BBitmap *bm = BTranslationUtils::GetBitmap (filename); return bm; } void * be_translate_bitmap_from_memory (const void *buf, size_t bytes) { BMemoryIO io (buf, bytes); BBitmap *bm = BTranslationUtils::GetBitmap (&io); return bm; } #endif /* Return the size of BITMAP's data, in bytes. */ size_t BBitmap_bytes_length (void *bitmap) { BBitmap *bm = (BBitmap *) bitmap; return bm->BitsLength (); } /* Show VIEW's tooltip. */ void BView_show_tooltip (void *view) { BView *vw = (BView *) view; if (vw->LockLooper ()) { vw->ShowToolTip (vw->ToolTip ()); vw->UnlockLooper (); } } #ifdef USE_BE_CAIRO /* Return VIEW's cairo context. */ cairo_t * EmacsView_cairo_context (void *view) { EmacsView *vw = (EmacsView *) view; return vw->cr_context; } /* Transfer each clip rectangle in VIEW to the cairo context CTX. */ void BView_cr_dump_clipping (void *view, cairo_t *ctx) { BView *vw = (BView *) find_appropriate_view_for_draw (view); BRegion cr; vw->GetClippingRegion (&cr); for (int i = 0; i < cr.CountRects (); ++i) { BRect r = cr.RectAt (i); cairo_rectangle (ctx, r.left, r.top, BE_RECT_WIDTH (r), BE_RECT_HEIGHT (r)); } cairo_clip (ctx); } /* Lock WINDOW in preparation for drawing using Cairo. */ void EmacsWindow_begin_cr_critical_section (void *window) { BWindow *w = (BWindow *) window; BView *vw = (BView *) w->FindView ("Emacs"); EmacsView *ev = dynamic_cast (vw); if (ev && !ev->cr_surface_lock.Lock ()) gui_abort ("Couldn't lock view cairo surface"); } /* Unlock WINDOW in preparation for drawing using Cairo. */ void EmacsWindow_end_cr_critical_section (void *window) { BWindow *w = (BWindow *) window; BView *vw = (BView *) w->FindView ("Emacs"); EmacsView *ev = dynamic_cast (vw); if (ev) ev->cr_surface_lock.Unlock (); } #endif /* Get the width of STR in the plain font. */ int be_string_width_with_plain_font (const char *str) { return be_plain_font->StringWidth (str); } /* Get the ascent + descent of the plain font. */ int be_plain_font_height (void) { struct font_height fheight; be_plain_font->GetHeight (&fheight); return fheight.ascent + fheight.descent; } /* Return the number of physical displays connected. */ int be_get_display_screens (void) { int count = 1; BScreen scr; if (!scr.IsValid ()) gui_abort ("Main screen vanished!"); while (scr.SetToNext () == B_OK && scr.IsValid ()) ++count; return count; } /* Set the minimum width the user can resize WINDOW to. */ void BWindow_set_min_size (void *window, int width, int height) { BWindow *w = (BWindow *) window; if (!w->LockLooper ()) gui_abort ("Failed to lock window looper setting min size"); w->SetSizeLimits (width, -1, height, -1); w->UnlockLooper (); } /* Synchronize WINDOW's connection to the App Server. */ void BWindow_sync (void *window) { BWindow *w = (BWindow *) window; if (!w->LockLooper ()) gui_abort ("Failed to lock window looper for sync"); w->Sync (); w->UnlockLooper (); } /* Set the alignment of WINDOW's dimensions. */ void BWindow_set_size_alignment (void *window, int align_width, int align_height) { BWindow *w = (BWindow *) window; if (!w->LockLooper ()) gui_abort ("Failed to lock window looper setting alignment"); #if 0 /* Haiku does not currently implement SetWindowAlignment. */ if (w->SetWindowAlignment (B_PIXEL_ALIGNMENT, -1, -1, align_width, align_width, -1, -1, align_height, align_height) != B_NO_ERROR) gui_abort ("Invalid pixel alignment"); #endif w->UnlockLooper (); } void BWindow_send_behind (void *window, void *other_window) { BWindow *w = (BWindow *) window; BWindow *other = (BWindow *) other_window; if (!w->LockLooper ()) gui_abort ("Failed to lock window in order to send it behind another"); w->SendBehind (other); w->UnlockLooper (); } bool BWindow_is_active (void *window) { BWindow *w = (BWindow *) window; return w->IsActive (); } bool be_use_subpixel_antialiasing (void) { bool current_subpixel_antialiasing; if (get_subpixel_antialiasing (¤t_subpixel_antialiasing) != B_OK) return false; return current_subpixel_antialiasing; } void BWindow_set_override_redirect (void *window, bool override_redirect_p) { EmacsWindow *w = (EmacsWindow *) window; if (w->LockLooper ()) { if (override_redirect_p && !w->override_redirect_p) { w->override_redirect_p = true; w->pre_override_redirect_feel = w->Feel (); w->pre_override_redirect_look = w->Look (); w->SetFeel (kMenuWindowFeel); w->SetLook (B_NO_BORDER_WINDOW_LOOK); w->pre_override_redirect_workspaces = w->Workspaces (); w->SetWorkspaces (B_ALL_WORKSPACES); } else if (w->override_redirect_p) { w->override_redirect_p = false; w->SetFeel (w->pre_override_redirect_feel); w->SetLook (w->pre_override_redirect_look); w->SetWorkspaces (w->pre_override_redirect_workspaces); } w->UnlockLooper (); } } /* Find a resource by the name NAME inside the settings file. The string returned is in UTF-8 encoding, and will stay allocated as long as the BApplication (a.k.a display) is alive. */ const char * be_find_setting (const char *name) { Emacs *app = (Emacs *) be_app; const char *value; /* Note that this is thread-safe since the constructor of `Emacs' runs in the main thread. */ if (!app->settings_valid_p) return NULL; if (app->settings.FindString (name, 0, &value) != B_OK) return NULL; return value; } void EmacsWindow_signal_menu_update_complete (void *window) { EmacsWindow *w = (EmacsWindow *) window; pthread_mutex_lock (&w->menu_update_mutex); w->menu_updated_p = true; pthread_cond_signal (&w->menu_update_cv); pthread_mutex_unlock (&w->menu_update_mutex); } void BMessage_delete (void *message) { delete (BMessage *) message; } static int32 be_drag_message_thread_entry (void *thread_data) { BMessenger *messenger; BMessage reply; messenger = (BMessenger *) thread_data; messenger->SendMessage (WAIT_FOR_RELEASE, &reply); return 0; } void be_drag_message (void *view, void *message, void (*block_input_function) (void), void (*unblock_input_function) (void), void (*process_pending_signals_function) (void)) { EmacsView *vw = (EmacsView *) view; BMessage *msg = (BMessage *) message; BMessage wait_for_release; BMessenger messenger (vw); struct object_wait_info infos[2]; ssize_t stat; block_input_function (); if (!vw->LockLooper ()) gui_abort ("Failed to lock view looper for drag"); vw->DragMessage (msg, BRect (0, 0, 0, 0)); vw->UnlockLooper (); infos[0].object = port_application_to_emacs; infos[0].type = B_OBJECT_TYPE_PORT; infos[0].events = B_EVENT_READ; infos[1].object = spawn_thread (be_drag_message_thread_entry, "Drag waiter thread", B_DEFAULT_MEDIA_PRIORITY, (void *) &messenger); infos[1].type = B_OBJECT_TYPE_THREAD; infos[1].events = B_EVENT_INVALID; unblock_input_function (); if (infos[1].object < B_OK) return; block_input_function (); resume_thread (infos[1].object); unblock_input_function (); while (true) { block_input_function (); stat = wait_for_objects ((struct object_wait_info *) &infos, 2); unblock_input_function (); if (stat == B_INTERRUPTED || stat == B_TIMED_OUT || stat == B_WOULD_BLOCK) continue; if (stat < B_OK) gui_abort ("Failed to wait for drag"); if (infos[0].events & B_EVENT_READ) process_pending_signals_function (); if (infos[1].events & B_EVENT_INVALID) return; infos[0].events = B_EVENT_READ; infos[1].events = B_EVENT_INVALID; } }