diff options
Diffstat (limited to 'unix/gtkwin.c')
-rw-r--r-- | unix/gtkwin.c | 5431 |
1 files changed, 0 insertions, 5431 deletions
diff --git a/unix/gtkwin.c b/unix/gtkwin.c deleted file mode 100644 index 61e77187..00000000 --- a/unix/gtkwin.c +++ /dev/null @@ -1,5431 +0,0 @@ -/* - * gtkwin.c: the main code that runs a PuTTY terminal emulator and - * backend in a GTK window. - */ - -#define _GNU_SOURCE - -#include <string.h> -#include <assert.h> -#include <stdlib.h> -#include <string.h> -#include <signal.h> -#include <stdio.h> -#include <time.h> -#include <errno.h> -#include <locale.h> -#include <fcntl.h> -#include <unistd.h> -#include <sys/types.h> -#include <sys/wait.h> -#include <gtk/gtk.h> -#if !GTK_CHECK_VERSION(3,0,0) -#include <gdk/gdkkeysyms.h> -#endif - -#if GTK_CHECK_VERSION(2,0,0) -#include <gtk/gtkimmodule.h> -#endif - -#define MAY_REFER_TO_GTK_IN_HEADERS - -#include "putty.h" -#include "terminal.h" -#include "gtkcompat.h" -#include "gtkfont.h" -#include "gtkmisc.h" - -#ifndef NOT_X_WINDOWS -#include <gdk/gdkx.h> -#include <X11/Xlib.h> -#include <X11/Xutil.h> -#include <X11/Xatom.h> -#endif - -#include "x11misc.h" - -GdkAtom compound_text_atom, utf8_string_atom; -static GdkAtom clipboard_atom -#if GTK_CHECK_VERSION(2,0,0) /* GTK1 will have to fill this in at startup */ - = GDK_SELECTION_CLIPBOARD -#endif - ; - -#ifdef JUST_USE_GTK_CLIPBOARD_UTF8 -/* - * Because calling gtk_clipboard_set_with_data triggers a call to the - * clipboard_clear function from the last time, we need to arrange a - * way to distinguish a real call to clipboard_clear for the _new_ - * instance of the clipboard data from the leftover call for the - * outgoing one. We do this by setting the user data field in our - * gtk_clipboard_set_with_data() call, instead of the obvious pointer - * to 'inst', to one of these. - */ -struct clipboard_data_instance { - char *pasteout_data_utf8; - int pasteout_data_utf8_len; - struct clipboard_state *state; - struct clipboard_data_instance *next, *prev; -}; -#endif - -struct clipboard_state { - GtkFrontend *inst; - int clipboard; - GdkAtom atom; -#ifdef JUST_USE_GTK_CLIPBOARD_UTF8 - GtkClipboard *gtkclipboard; - struct clipboard_data_instance *current_cdi; -#else - char *pasteout_data, *pasteout_data_ctext, *pasteout_data_utf8; - int pasteout_data_len, pasteout_data_ctext_len, pasteout_data_utf8_len; -#endif -}; - -typedef struct XpmHolder XpmHolder; /* only used for GTK 1 */ - -struct GtkFrontend { - GtkWidget *window, *area, *sbar; - gboolean sbar_visible; - gboolean drawing_area_got_size, drawing_area_realised; - gboolean drawing_area_setup_needed; - GtkBox *hbox; - GtkAdjustment *sbar_adjust; - GtkWidget *menu, *specialsmenu, *specialsitem1, *specialsitem2, - *restartitem; - GtkWidget *sessionsmenu; -#ifndef NOT_X_WINDOWS - Display *disp; -#endif -#ifndef NO_BACKING_PIXMAPS - /* - * Server-side pixmap which we use to cache the terminal window's - * contents. When we draw text in the terminal, we draw it to this - * pixmap first, and then blit from there to the actual window; - * this way, X expose events can be handled with an absolute - * minimum of network traffic, by just sending a command to - * re-blit an appropriate rectangle from this pixmap. - */ - GdkPixmap *pixmap; -#endif -#ifdef DRAW_TEXT_CAIRO - /* - * If we're drawing using Cairo, we cache the same image on the - * client side in a Cairo surface. - * - * In GTK2+Cairo, this happens _as well_ as having the server-side - * pixmap cache above; in GTK3+Cairo, server-side pixmaps are - * deprecated, so we _just_ have this client-side cache. In the - * latter case that means we have to transmit a big wodge of - * bitmap data over the X connection on every expose event; but - * GTK3 apparently deliberately provides no way to avoid that - * inefficiency, and at least this way we don't _also_ have to - * redo any font rendering just because the window was temporarily - * covered. - */ - cairo_surface_t *surface; -#endif - int backing_w, backing_h; -#if GTK_CHECK_VERSION(2,0,0) - GtkIMContext *imc; -#endif - unifont *fonts[4]; /* normal, bold, wide, widebold */ - int xpos, ypos, gravity; - bool gotpos; - GdkCursor *rawcursor, *textcursor, *blankcursor, *waitcursor, *currcursor; - GdkColor cols[OSC4_NCOLOURS]; /* indexed by xterm colour indices */ -#if !GTK_CHECK_VERSION(3,0,0) - GdkColormap *colmap; -#endif - bool direct_to_font; - struct clipboard_state clipstates[N_CLIPBOARDS]; -#ifdef JUST_USE_GTK_CLIPBOARD_UTF8 - /* Remember all clipboard_data_instance structures currently - * associated with this GtkFrontend, in case they're still around - * when it gets destroyed */ - struct clipboard_data_instance cdi_headtail; -#endif - int clipboard_ctrlshiftins, clipboard_ctrlshiftcv; - int font_width, font_height; - int width, height, scale; - bool ignore_sbar; - bool mouseptr_visible; - BusyStatus busy_status; - int alt_keycode; - int alt_digits; - char *wintitle; - char *icontitle; - int master_fd, master_func_id; - Ldisc *ldisc; - Backend *backend; - Terminal *term; - LogContext *logctx; - bool exited; - struct unicode_data ucsdata; - Conf *conf; - eventlog_stuff *eventlogstuff; - guint32 input_event_time; /* Timestamp of the most recent input event. */ - GtkWidget *dialogs[DIALOG_SLOT_LIMIT]; -#if GTK_CHECK_VERSION(3,4,0) - gdouble cumulative_scroll; -#endif - /* Cached things out of conf that we refer to a lot */ - int bold_style; - int window_border; - int cursor_type; - int drawtype; - int meta_mod_mask; -#ifdef OSX_META_KEY_CONFIG - int system_mod_mask; -#endif - bool send_raw_mouse; - bool pointer_indicates_raw_mouse; - unifont_drawctx uctx; -#if GTK_CHECK_VERSION(2,0,0) - GdkPixbuf *trust_sigil_pb; -#else - GdkPixmap *trust_sigil_pm; -#endif - int trust_sigil_w, trust_sigil_h; - - Seat seat; - TermWin termwin; - LogPolicy logpolicy; -}; - -static void cache_conf_values(GtkFrontend *inst) -{ - inst->bold_style = conf_get_int(inst->conf, CONF_bold_style); - inst->window_border = conf_get_int(inst->conf, CONF_window_border); - inst->cursor_type = conf_get_int(inst->conf, CONF_cursor_type); -#ifdef OSX_META_KEY_CONFIG - inst->meta_mod_mask = 0; - if (conf_get_bool(inst->conf, CONF_osx_option_meta)) - inst->meta_mod_mask |= GDK_MOD1_MASK; - if (conf_get_bool(inst->conf, CONF_osx_command_meta)) - inst->meta_mod_mask |= GDK_MOD2_MASK; - inst->system_mod_mask = GDK_MOD2_MASK & ~inst->meta_mod_mask; -#else - inst->meta_mod_mask = GDK_MOD1_MASK; -#endif -} - -static void start_backend(GtkFrontend *inst); -static void exit_callback(void *vinst); -static void destroy_inst_connection(GtkFrontend *inst); -static void delete_inst(GtkFrontend *inst); - -static void post_fatal_message_box_toplevel(void *vctx) -{ - GtkFrontend *inst = (GtkFrontend *)vctx; - gtk_widget_destroy(inst->window); -} - -static void post_fatal_message_box(void *vctx, int result) -{ - GtkFrontend *inst = (GtkFrontend *)vctx; - unregister_dialog(&inst->seat, DIALOG_SLOT_CONNECTION_FATAL); - queue_toplevel_callback(post_fatal_message_box_toplevel, inst); -} - -static void common_connfatal_message_box( - GtkFrontend *inst, const char *msg, post_dialog_fn_t postfn) -{ - char *title = dupcat(appname, " Fatal Error"); - GtkWidget *dialog = create_message_box( - inst->window, title, msg, - string_width("REASONABLY LONG LINE OF TEXT FOR BASIC SANITY"), - false, &buttons_ok, postfn, inst); - register_dialog(&inst->seat, DIALOG_SLOT_CONNECTION_FATAL, dialog); - sfree(title); -} - -void fatal_message_box(GtkFrontend *inst, const char *msg) -{ - common_connfatal_message_box(inst, msg, post_fatal_message_box); -} - -static void connection_fatal_callback(void *vctx) -{ - GtkFrontend *inst = (GtkFrontend *)vctx; - destroy_inst_connection(inst); -} - -static void post_nonfatal_message_box(void *vctx, int result) -{ - GtkFrontend *inst = (GtkFrontend *)vctx; - unregister_dialog(&inst->seat, DIALOG_SLOT_CONNECTION_FATAL); -} - -static void gtk_seat_connection_fatal(Seat *seat, const char *msg) -{ - GtkFrontend *inst = container_of(seat, GtkFrontend, seat); - if (conf_get_int(inst->conf, CONF_close_on_exit) == FORCE_ON) { - fatal_message_box(inst, msg); - } else { - common_connfatal_message_box(inst, msg, post_nonfatal_message_box); - } - - inst->exited = true; /* suppress normal exit handling */ - queue_toplevel_callback(connection_fatal_callback, inst); -} - -/* - * Default settings that are specific to pterm. - */ -FontSpec *platform_default_fontspec(const char *name) -{ - if (!strcmp(name, "Font")) - return fontspec_new(DEFAULT_GTK_FONT); - else - return fontspec_new(""); -} - -Filename *platform_default_filename(const char *name) -{ - if (!strcmp(name, "LogFileName")) - return filename_from_str("putty.log"); - else - return filename_from_str(""); -} - -char *platform_default_s(const char *name) -{ - if (!strcmp(name, "SerialLine")) - return dupstr("/dev/ttyS0"); - return NULL; -} - -bool platform_default_b(const char *name, bool def) -{ - if (!strcmp(name, "WinNameAlways")) { - /* X natively supports icon titles, so use 'em by default */ - return false; - } - return def; -} - -int platform_default_i(const char *name, int def) -{ - if (!strcmp(name, "CloseOnExit")) - return 2; /* maps to FORCE_ON after painful rearrangement :-( */ - return def; -} - -static char *gtk_seat_get_ttymode(Seat *seat, const char *mode) -{ - GtkFrontend *inst = container_of(seat, GtkFrontend, seat); - return term_get_ttymode(inst->term, mode); -} - -static size_t gtk_seat_output(Seat *seat, bool is_stderr, - const void *data, size_t len) -{ - GtkFrontend *inst = container_of(seat, GtkFrontend, seat); - return term_data(inst->term, is_stderr, data, len); -} - -static bool gtk_seat_eof(Seat *seat) -{ - /* GtkFrontend *inst = container_of(seat, GtkFrontend, seat); */ - return true; /* do respond to incoming EOF with outgoing */ -} - -static int gtk_seat_get_userpass_input(Seat *seat, prompts_t *p, - bufchain *input) -{ - GtkFrontend *inst = container_of(seat, GtkFrontend, seat); - int ret; - ret = cmdline_get_passwd_input(p); - if (ret == -1) - ret = term_get_userpass_input(inst->term, p, input); - return ret; -} - -static bool gtk_seat_is_utf8(Seat *seat) -{ - GtkFrontend *inst = container_of(seat, GtkFrontend, seat); - return inst->ucsdata.line_codepage == CS_UTF8; -} - -static void get_window_pixel_size(GtkFrontend *inst, int *w, int *h) -{ - /* - * I assume that when the GTK version of this call is available - * we should use it. Not sure how it differs from the GDK one, - * though. - */ -#if GTK_CHECK_VERSION(2,0,0) - gtk_window_get_size(GTK_WINDOW(inst->window), w, h); -#else - gdk_window_get_size(gtk_widget_get_window(inst->window), w, h); -#endif -} - -static bool gtk_seat_get_window_pixel_size(Seat *seat, int *w, int *h) -{ - GtkFrontend *inst = container_of(seat, GtkFrontend, seat); - get_window_pixel_size(inst, w, h); - return true; -} - -StripCtrlChars *gtk_seat_stripctrl_new( - Seat *seat, BinarySink *bs_out, SeatInteractionContext sic) -{ - GtkFrontend *inst = container_of(seat, GtkFrontend, seat); - return stripctrl_new_term(bs_out, false, 0, inst->term); -} - -static void gtk_seat_notify_remote_exit(Seat *seat); -static void gtk_seat_update_specials_menu(Seat *seat); -static void gtk_seat_set_busy_status(Seat *seat, BusyStatus status); -static const char *gtk_seat_get_x_display(Seat *seat); -#ifndef NOT_X_WINDOWS -static bool gtk_seat_get_windowid(Seat *seat, long *id); -#endif -static bool gtk_seat_set_trust_status(Seat *seat, bool trusted); -static bool gtk_seat_get_cursor_position(Seat *seat, int *x, int *y); - -static const SeatVtable gtk_seat_vt = { - .output = gtk_seat_output, - .eof = gtk_seat_eof, - .get_userpass_input = gtk_seat_get_userpass_input, - .notify_remote_exit = gtk_seat_notify_remote_exit, - .connection_fatal = gtk_seat_connection_fatal, - .update_specials_menu = gtk_seat_update_specials_menu, - .get_ttymode = gtk_seat_get_ttymode, - .set_busy_status = gtk_seat_set_busy_status, - .verify_ssh_host_key = gtk_seat_verify_ssh_host_key, - .confirm_weak_crypto_primitive = gtk_seat_confirm_weak_crypto_primitive, - .confirm_weak_cached_hostkey = gtk_seat_confirm_weak_cached_hostkey, - .is_utf8 = gtk_seat_is_utf8, - .echoedit_update = nullseat_echoedit_update, - .get_x_display = gtk_seat_get_x_display, -#ifdef NOT_X_WINDOWS - .get_windowid = nullseat_get_windowid, -#else - .get_windowid = gtk_seat_get_windowid, -#endif - .get_window_pixel_size = gtk_seat_get_window_pixel_size, - .stripctrl_new = gtk_seat_stripctrl_new, - .set_trust_status = gtk_seat_set_trust_status, - .verbose = nullseat_verbose_yes, - .interactive = nullseat_interactive_yes, - .get_cursor_position = gtk_seat_get_cursor_position, -}; - -static void gtk_eventlog(LogPolicy *lp, const char *string) -{ - GtkFrontend *inst = container_of(lp, GtkFrontend, logpolicy); - logevent_dlg(inst->eventlogstuff, string); -} - -static int gtk_askappend(LogPolicy *lp, Filename *filename, - void (*callback)(void *ctx, int result), void *ctx) -{ - GtkFrontend *inst = container_of(lp, GtkFrontend, logpolicy); - return gtkdlg_askappend(&inst->seat, filename, callback, ctx); -} - -static void gtk_logging_error(LogPolicy *lp, const char *event) -{ - GtkFrontend *inst = container_of(lp, GtkFrontend, logpolicy); - - /* Send 'can't open log file' errors to the terminal window. - * (Marked as stderr, although terminal.c won't care.) */ - seat_stderr_pl(&inst->seat, ptrlen_from_asciz(event)); - seat_stderr_pl(&inst->seat, PTRLEN_LITERAL("\r\n")); -} - -static const LogPolicyVtable gtk_logpolicy_vt = { - .eventlog = gtk_eventlog, - .askappend = gtk_askappend, - .logging_error = gtk_logging_error, - .verbose = null_lp_verbose_yes, -}; - -/* - * Translate a raw mouse button designation (LEFT, MIDDLE, RIGHT) - * into a cooked one (SELECT, EXTEND, PASTE). - * - * In Unix, this is not configurable; the X button arrangement is - * rock-solid across all applications, everyone has a three-button - * mouse or a means of faking it, and there is no need to switch - * buttons around at all. - */ -static Mouse_Button translate_button(Mouse_Button button) -{ - if (button == MBT_LEFT) - return MBT_SELECT; - if (button == MBT_MIDDLE) - return MBT_PASTE; - if (button == MBT_RIGHT) - return MBT_EXTEND; - return 0; /* shouldn't happen */ -} - -/* - * Return the top-level GtkWindow associated with a particular - * front end instance. - */ -GtkWidget *gtk_seat_get_window(Seat *seat) -{ - GtkFrontend *inst = container_of(seat, GtkFrontend, seat); - return inst->window; -} - -/* - * Set and clear a pointer to a dialog box created as a result of the - * network code wanting to ask an asynchronous user question (e.g. - * 'what about this dodgy host key, then?'). - */ -void register_dialog(Seat *seat, enum DialogSlot slot, GtkWidget *dialog) -{ - GtkFrontend *inst; - assert(seat->vt == >k_seat_vt); - inst = container_of(seat, GtkFrontend, seat); - assert(slot < DIALOG_SLOT_LIMIT); - assert(!inst->dialogs[slot]); - inst->dialogs[slot] = dialog; -} -void unregister_dialog(Seat *seat, enum DialogSlot slot) -{ - GtkFrontend *inst; - assert(seat->vt == >k_seat_vt); - inst = container_of(seat, GtkFrontend, seat); - assert(slot < DIALOG_SLOT_LIMIT); - assert(inst->dialogs[slot]); - inst->dialogs[slot] = NULL; -} - -/* - * Minimise or restore the window in response to a server-side - * request. - */ -static void gtkwin_set_minimised(TermWin *tw, bool minimised) -{ - /* - * GTK 1.2 doesn't know how to do this. - */ -#if GTK_CHECK_VERSION(2,0,0) - GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); - if (minimised) - gtk_window_iconify(GTK_WINDOW(inst->window)); - else - gtk_window_deiconify(GTK_WINDOW(inst->window)); -#endif -} - -/* - * Move the window in response to a server-side request. - */ -static void gtkwin_move(TermWin *tw, int x, int y) -{ - GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); - /* - * I assume that when the GTK version of this call is available - * we should use it. Not sure how it differs from the GDK one, - * though. - */ -#if GTK_CHECK_VERSION(2,0,0) - /* in case we reset this at startup due to a geometry string */ - gtk_window_set_gravity(GTK_WINDOW(inst->window), GDK_GRAVITY_NORTH_EAST); - gtk_window_move(GTK_WINDOW(inst->window), x, y); -#else - gdk_window_move(gtk_widget_get_window(inst->window), x, y); -#endif -} - -/* - * Move the window to the top or bottom of the z-order in response - * to a server-side request. - */ -static void gtkwin_set_zorder(TermWin *tw, bool top) -{ - GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); - if (top) - gdk_window_raise(gtk_widget_get_window(inst->window)); - else - gdk_window_lower(gtk_widget_get_window(inst->window)); -} - -/* - * Refresh the window in response to a server-side request. - */ -static void gtkwin_refresh(TermWin *tw) -{ - GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); - term_invalidate(inst->term); -} - -/* - * Maximise or restore the window in response to a server-side - * request. - */ -static void gtkwin_set_maximised(TermWin *tw, bool maximised) -{ - /* - * GTK 1.2 doesn't know how to do this. - */ -#if GTK_CHECK_VERSION(2,0,0) - GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); - if (maximised) - gtk_window_maximize(GTK_WINDOW(inst->window)); - else - gtk_window_unmaximize(GTK_WINDOW(inst->window)); -#endif -} - -/* - * Find out whether a dialog box already exists for this window in a - * particular DialogSlot. If it does, uniconify it (if we can) and - * raise it, so that the user realises they've already been asked this - * question. - */ -static bool find_and_raise_dialog(GtkFrontend *inst, enum DialogSlot slot) -{ - GtkWidget *dialog = inst->dialogs[slot]; - if (!dialog) - return false; - -#if GTK_CHECK_VERSION(2,0,0) - gtk_window_deiconify(GTK_WINDOW(dialog)); -#endif - gdk_window_raise(gtk_widget_get_window(dialog)); - return true; -} - -static void warn_on_close_callback(void *vctx, int result) -{ - GtkFrontend *inst = (GtkFrontend *)vctx; - unregister_dialog(&inst->seat, DIALOG_SLOT_WARN_ON_CLOSE); - if (result) - gtk_widget_destroy(inst->window); -} - -/* - * Handle the 'delete window' event (e.g. user clicking the WM close - * button). The return value false means the window should close, and - * true means it shouldn't. - * - * (That's counterintuitive, but really, in GTK terms, true means 'I - * have done everything necessary to handle this event, so the default - * handler need not do anything', i.e. 'suppress default handler', - * i.e. 'do not close the window'.) - */ -gint delete_window(GtkWidget *widget, GdkEvent *event, GtkFrontend *inst) -{ - if (!inst->exited && conf_get_bool(inst->conf, CONF_warn_on_close)) { - /* - * We're not going to exit right now. We must put up a - * warn-on-close dialog, unless one already exists, in which - * case we'll just re-emphasise that one. - */ - if (!find_and_raise_dialog(inst, DIALOG_SLOT_WARN_ON_CLOSE)) { - char *title = dupcat(appname, " Exit Confirmation"); - char *msg, *additional = NULL; - if (inst->backend && inst->backend->vt->close_warn_text) { - additional = inst->backend->vt->close_warn_text(inst->backend); - } - msg = dupprintf("Are you sure you want to close this session?%s%s", - additional ? "\n" : "", - additional ? additional : ""); - GtkWidget *dialog = create_message_box( - inst->window, title, msg, - string_width("Most of the width of the above text"), - false, &buttons_yn, warn_on_close_callback, inst); - register_dialog(&inst->seat, DIALOG_SLOT_WARN_ON_CLOSE, dialog); - sfree(title); - sfree(msg); - sfree(additional); - } - return true; - } - return false; -} - -#if GTK_CHECK_VERSION(2,0,0) -static void window_state_event(GtkWidget *widget, GdkEventWindowState *event, - gpointer user_data) -{ - GtkFrontend *inst = (GtkFrontend *)user_data; - term_notify_minimised( - inst->term, event->new_window_state & GDK_WINDOW_STATE_ICONIFIED); -} -#endif - -static void update_mouseptr(GtkFrontend *inst) -{ - switch (inst->busy_status) { - case BUSY_NOT: - if (!inst->mouseptr_visible) { - gdk_window_set_cursor(gtk_widget_get_window(inst->area), - inst->blankcursor); - } else if (inst->pointer_indicates_raw_mouse) { - gdk_window_set_cursor(gtk_widget_get_window(inst->area), - inst->rawcursor); - } else { - gdk_window_set_cursor(gtk_widget_get_window(inst->area), - inst->textcursor); - } - break; - case BUSY_WAITING: /* XXX can we do better? */ - case BUSY_CPU: - /* We always display these cursors. */ - gdk_window_set_cursor(gtk_widget_get_window(inst->area), - inst->waitcursor); - break; - default: - unreachable("Bad busy_status"); - } -} - -static void show_mouseptr(GtkFrontend *inst, bool show) -{ - if (!conf_get_bool(inst->conf, CONF_hide_mouseptr)) - show = true; - inst->mouseptr_visible = show; - update_mouseptr(inst); -} - -static void draw_backing_rect(GtkFrontend *inst); - -static void drawing_area_setup(GtkFrontend *inst, int width, int height) -{ - int w, h, new_scale; - bool need_size = false; - - /* - * See if the terminal size has changed. - */ - w = (width - 2*inst->window_border) / inst->font_width; - h = (height - 2*inst->window_border) / inst->font_height; - if (w != inst->width || h != inst->height) { - /* - * Update conf. - */ - inst->width = w; - inst->height = h; - conf_set_int(inst->conf, CONF_width, inst->width); - conf_set_int(inst->conf, CONF_height, inst->height); - /* - * We'll need to tell terminal.c about the resize below. - */ - need_size = true; - /* - * And we must refresh the window's backing image. - */ - inst->drawing_area_setup_needed = true; - } - -#if GTK_CHECK_VERSION(3,10,0) - new_scale = gtk_widget_get_scale_factor(inst->area); - if (new_scale != inst->scale) - inst->drawing_area_setup_needed = true; -#else - new_scale = 1; -#endif - - int new_backing_w = w * inst->font_width + 2*inst->window_border; - int new_backing_h = h * inst->font_height + 2*inst->window_border; - new_backing_w *= new_scale; - new_backing_h *= new_scale; - - if (inst->backing_w != new_backing_w || inst->backing_h != new_backing_h) - inst->drawing_area_setup_needed = true; - - /* - * This event might be spurious; some GTK setups have been known - * to call it when nothing at all has changed. Check if we have - * any reason to proceed. - */ - if (!inst->drawing_area_setup_needed) - return; - - inst->drawing_area_setup_needed = false; - inst->scale = new_scale; - inst->backing_w = new_backing_w; - inst->backing_h = new_backing_h; - -#ifndef NO_BACKING_PIXMAPS - if (inst->pixmap) { - gdk_pixmap_unref(inst->pixmap); - inst->pixmap = NULL; - } - - inst->pixmap = gdk_pixmap_new(gtk_widget_get_window(inst->area), - inst->backing_w, inst->backing_h, -1); -#endif - -#ifdef DRAW_TEXT_CAIRO - if (inst->surface) { - cairo_surface_destroy(inst->surface); - inst->surface = NULL; - } - - inst->surface = cairo_image_surface_create( - CAIRO_FORMAT_ARGB32, inst->backing_w, inst->backing_h); -#endif - - draw_backing_rect(inst); - - if (need_size && inst->term) { - term_size(inst->term, h, w, conf_get_int(inst->conf, CONF_savelines)); - } - - if (inst->term) - term_invalidate(inst->term); - -#if GTK_CHECK_VERSION(2,0,0) - gtk_im_context_set_client_window( - inst->imc, gtk_widget_get_window(inst->area)); -#endif -} - -static void drawing_area_setup_simple(GtkFrontend *inst) -{ - /* - * Wrapper on drawing_area_setup which fetches the width and - * height of the drawing area. We go directly to the inner version - * in the case where a new size allocation comes in (just in case - * GTK hasn't installed it in the normal place yet). - */ -#if GTK_CHECK_VERSION(2,0,0) - GdkRectangle alloc; - gtk_widget_get_allocation(inst->area, &alloc); -#else - GtkAllocation alloc = inst->area->allocation; -#endif - drawing_area_setup(inst, alloc.width, alloc.height); -} - -static void area_realised(GtkWidget *widget, GtkFrontend *inst) -{ - inst->drawing_area_realised = true; - if (inst->drawing_area_realised && inst->drawing_area_got_size && - inst->drawing_area_setup_needed) - drawing_area_setup_simple(inst); -} - -static void area_size_allocate( - GtkWidget *widget, GdkRectangle *alloc, GtkFrontend *inst) -{ - inst->drawing_area_got_size = true; - if (inst->drawing_area_realised && inst->drawing_area_got_size) - drawing_area_setup(inst, alloc->width, alloc->height); -} - -#if GTK_CHECK_VERSION(3,10,0) -static void area_check_scale(GtkFrontend *inst) -{ - if (!inst->drawing_area_setup_needed && - inst->scale != gtk_widget_get_scale_factor(inst->area)) { - drawing_area_setup_simple(inst); - if (inst->term) { - term_invalidate(inst->term); - term_update(inst->term); - } - } -} -#endif - -static gboolean window_configured( - GtkWidget *widget, GdkEventConfigure *event, gpointer data) -{ - GtkFrontend *inst = (GtkFrontend *)data; - if (inst->term) { - term_notify_window_pos(inst->term, event->x, event->y); - term_notify_window_size_pixels( - inst->term, event->width, event->height); - } - return false; -} - -#if GTK_CHECK_VERSION(3,10,0) -static gboolean area_configured( - GtkWidget *widget, GdkEventConfigure *event, gpointer data) -{ - GtkFrontend *inst = (GtkFrontend *)data; - area_check_scale(inst); - return false; -} -#endif - -#ifdef DRAW_TEXT_CAIRO -static void cairo_setup_draw_ctx(GtkFrontend *inst) -{ - cairo_get_matrix(inst->uctx.u.cairo.cr, - &inst->uctx.u.cairo.origmatrix); - cairo_set_line_width(inst->uctx.u.cairo.cr, 1.0); - cairo_set_line_cap(inst->uctx.u.cairo.cr, CAIRO_LINE_CAP_SQUARE); - cairo_set_line_join(inst->uctx.u.cairo.cr, CAIRO_LINE_JOIN_MITER); - /* This antialiasing setting appears to be ignored for Pango - * font rendering but honoured for stroking and filling paths; - * I don't quite understand the logic of that, but I won't - * complain since it's exactly what I happen to want */ - cairo_set_antialias(inst->uctx.u.cairo.cr, CAIRO_ANTIALIAS_NONE); -} -#endif - -#if GTK_CHECK_VERSION(3,0,0) -static gint draw_area(GtkWidget *widget, cairo_t *cr, gpointer data) -{ - GtkFrontend *inst = (GtkFrontend *)data; - -#if GTK_CHECK_VERSION(3,10,0) - /* - * This may be the first we hear of the window scale having - * changed, in which case we must hastily reconstruct our backing - * surface before we copy the wrong one into the newly resized - * real window. - */ - area_check_scale(inst); -#endif - - /* - * GTK3 window redraw: we always expect Cairo to be enabled, so - * that inst->surface exists, and pixmaps to be disabled, so that - * inst->pixmap does not exist. Hence, we just blit from - * inst->surface to the window. - */ - if (inst->surface) { - GdkRectangle dirtyrect; - cairo_surface_t *target_surface; - double orig_sx, orig_sy; - cairo_matrix_t m; - - /* - * Furtle around in the Cairo setup to force the device scale - * back to 1, so that when we blit a collection of pixels from - * our backing surface into the window, they really are - * _pixels_ and not some confusing antialiased slightly-offset - * 2x2 rectangle of pixeloids. - * - * I have no idea whether GTK expects me not to mess with the - * device scale in the cairo_surface_t backing its window, so - * I carefully put it back when I've finished. - * - * In some GTK setups, the Cairo context we're given may not - * have a zero translation offset in its matrix, in which case - * we have to adjust that to compensate for the change of - * scale, or else the old translation offset (designed for the - * old scale) will be multiplied by the new scale instead and - * put everything in the wrong place. - */ - target_surface = cairo_get_target(cr); - cairo_get_matrix(cr, &m); - cairo_surface_get_device_scale(target_surface, &orig_sx, &orig_sy); - cairo_surface_set_device_scale(target_surface, 1.0, 1.0); - cairo_translate(cr, m.x0 * (orig_sx - 1.0), m.y0 * (orig_sy - 1.0)); - - gdk_cairo_get_clip_rectangle(cr, &dirtyrect); - - cairo_set_source_surface(cr, inst->surface, 0, 0); - cairo_rectangle(cr, dirtyrect.x, dirtyrect.y, - dirtyrect.width, dirtyrect.height); - cairo_fill(cr); - - cairo_surface_set_device_scale(target_surface, orig_sx, orig_sy); - } - - return true; -} -#else -gint expose_area(GtkWidget *widget, GdkEventExpose *event, gpointer data) -{ - GtkFrontend *inst = (GtkFrontend *)data; - -#ifndef NO_BACKING_PIXMAPS - /* - * Draw to the exposed part of the window from the server-side - * backing pixmap. - */ - if (inst->pixmap) { - gdk_draw_pixmap(gtk_widget_get_window(widget), - (gtk_widget_get_style(widget)->fg_gc - [gtk_widget_get_state(widget)]), - inst->pixmap, - event->area.x, event->area.y, - event->area.x, event->area.y, - event->area.width, event->area.height); - } -#else - /* - * Failing that, draw from the client-side Cairo surface. (We - * should never be compiled in a context where we have _neither_ - * inst->surface nor inst->pixmap.) - */ - if (inst->surface) { - cairo_t *cr = gdk_cairo_create(gtk_widget_get_window(widget)); - cairo_set_source_surface(cr, inst->surface, 0, 0); - cairo_rectangle(cr, event->area.x, event->area.y, - event->area.width, event->area.height); - cairo_fill(cr); - cairo_destroy(cr); - } -#endif - - return true; -} -#endif - -#define KEY_PRESSED(k) \ - (inst->keystate[(k) / 32] & (1 << ((k) % 32))) - -#ifdef KEY_EVENT_DIAGNOSTICS -char *dup_keyval_name(guint keyval) -{ - const char *name = gdk_keyval_name(keyval); - if (name) - return dupstr(name); - else - return dupprintf("UNKNOWN[%u]", (unsigned)keyval); -} -#endif - -static void change_font_size(GtkFrontend *inst, int increment); -static void key_pressed(GtkFrontend *inst); - -/* Subroutine used in key_event */ -static int return_key(GtkFrontend *inst, char *output, bool *special) -{ - int end; - - /* Ugly label so we can come here as a fallback from - * numeric keypad Enter handling */ - if (inst->term->cr_lf_return) { -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - Return in cr_lf_return mode, translating as 0d 0a\n"); -#endif - output[1] = '\015'; - output[2] = '\012'; - end = 3; - } else { -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - Return special case, translating as 0d + special\n"); -#endif - output[1] = '\015'; - end = 2; - *special = true; - } - - return end; -} - -gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data) -{ - GtkFrontend *inst = (GtkFrontend *)data; - char output[256]; - wchar_t ucsoutput[2]; - int ucsval, start, end, output_charset; - bool special, use_ucsoutput; - bool force_format_numeric_keypad = false; - bool generated_something = false; - char num_keypad_key = '\0'; - const char *event_string = event->string ? event->string : ""; - - noise_ultralight(NOISE_SOURCE_KEY, event->keyval); - -#ifdef OSX_META_KEY_CONFIG - if (event->state & inst->system_mod_mask) - return false; /* let GTK process OS X Command key */ -#endif - - /* Remember the timestamp. */ - inst->input_event_time = event->time; - - /* By default, nothing is generated. */ - end = start = 0; - special = use_ucsoutput = false; - output_charset = CS_ISO8859_1; - -#ifdef KEY_EVENT_DIAGNOSTICS - { - char *type_string, *state_string, *keyval_string, *string_string; - - type_string = (event->type == GDK_KEY_PRESS ? dupstr("PRESS") : - event->type == GDK_KEY_RELEASE ? dupstr("RELEASE") : - dupprintf("UNKNOWN[%d]", (int)event->type)); - - { - static const struct { - int mod_bit; - const char *name; - } mod_bits[] = { - {GDK_SHIFT_MASK, "SHIFT"}, - {GDK_LOCK_MASK, "LOCK"}, - {GDK_CONTROL_MASK, "CONTROL"}, - {GDK_MOD1_MASK, "MOD1"}, - {GDK_MOD2_MASK, "MOD2"}, - {GDK_MOD3_MASK, "MOD3"}, - {GDK_MOD4_MASK, "MOD4"}, - {GDK_MOD5_MASK, "MOD5"}, - {GDK_SUPER_MASK, "SUPER"}, - {GDK_HYPER_MASK, "HYPER"}, - {GDK_META_MASK, "META"}, - }; - int i; - int val = event->state; - - state_string = dupstr(""); - - for (i = 0; i < lenof(mod_bits); i++) { - if (val & mod_bits[i].mod_bit) { - char *old = state_string; - state_string = dupcat(state_string, - state_string[0] ? "|" : "", - mod_bits[i].name); - sfree(old); - - val &= ~mod_bits[i].mod_bit; - } - } - - if (val || !state_string[0]) { - char *old = state_string; - state_string = dupprintf("%s%s%d", state_string, - state_string[0] ? "|" : "", val); - sfree(old); - } - } - - keyval_string = dup_keyval_name(event->keyval); - - string_string = dupstr(""); - { - int i; - for (i = 0; event_string[i]; i++) { - char *old = string_string; - string_string = dupprintf("%s%s%02x", string_string, - string_string[0] ? " " : "", - (unsigned)event_string[i] & 0xFF); - sfree(old); - } - } - - debug("key_event: type=%s keyval=%s state=%s " - "hardware_keycode=%d is_modifier=%s string=[%s]\n", - type_string, keyval_string, state_string, - (int)event->hardware_keycode, - event->is_modifier ? "true" : "false", - string_string); - - sfree(type_string); - sfree(state_string); - sfree(keyval_string); - sfree(string_string); - } -#endif /* KEY_EVENT_DIAGNOSTICS */ - - /* - * If Alt is being released after typing an Alt+numberpad - * sequence, we should generate the code that was typed. - * - * Note that we only do this if more than one key was actually - * pressed - I don't think Alt+NumPad4 should be ^D or that - * Alt+NumPad3 should be ^C, for example. There's no serious - * inconvenience in having to type a zero before a single-digit - * character code. - */ - if (event->type == GDK_KEY_RELEASE) { - if ((event->keyval == GDK_KEY_Meta_L || - event->keyval == GDK_KEY_Meta_R || - event->keyval == GDK_KEY_Alt_L || - event->keyval == GDK_KEY_Alt_R) && - inst->alt_keycode >= 0 && inst->alt_digits > 1) { -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - modifier release terminates Alt+numberpad input, " - "keycode = %d\n", inst->alt_keycode); -#endif - /* - * FIXME: we might usefully try to do something clever here - * about interpreting the generated key code in a way that's - * appropriate to the line code page. - */ - output[0] = inst->alt_keycode; - end = 1; - goto done; - } -#if GTK_CHECK_VERSION(2,0,0) -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - key release, passing to IM\n"); -#endif - if (gtk_im_context_filter_keypress(inst->imc, event)) { -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - key release accepted by IM\n"); -#endif - return true; - } else { -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - key release not accepted by IM\n"); -#endif - } -#endif - } - - if (event->type == GDK_KEY_PRESS) { - /* - * If Alt has just been pressed, we start potentially - * accumulating an Alt+numberpad code. We do this by - * setting alt_keycode to -1 (nothing yet but plausible). - */ - if ((event->keyval == GDK_KEY_Meta_L || - event->keyval == GDK_KEY_Meta_R || - event->keyval == GDK_KEY_Alt_L || - event->keyval == GDK_KEY_Alt_R)) { -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - modifier press potentially begins Alt+numberpad " - "input\n"); -#endif - inst->alt_keycode = -1; - inst->alt_digits = 0; - goto done; /* this generates nothing else */ - } - - /* - * If we're seeing a numberpad key press with Meta down, - * consider adding it to alt_keycode if that's sensible. - * Anything _else_ with Meta down cancels any possibility - * of an ALT keycode: we set alt_keycode to -2. - */ - if ((event->state & inst->meta_mod_mask) && inst->alt_keycode != -2) { - int digit = -1; - switch (event->keyval) { - case GDK_KEY_KP_0: case GDK_KEY_KP_Insert: digit = 0; break; - case GDK_KEY_KP_1: case GDK_KEY_KP_End: digit = 1; break; - case GDK_KEY_KP_2: case GDK_KEY_KP_Down: digit = 2; break; - case GDK_KEY_KP_3: case GDK_KEY_KP_Page_Down: digit = 3; break; - case GDK_KEY_KP_4: case GDK_KEY_KP_Left: digit = 4; break; - case GDK_KEY_KP_5: case GDK_KEY_KP_Begin: digit = 5; break; - case GDK_KEY_KP_6: case GDK_KEY_KP_Right: digit = 6; break; - case GDK_KEY_KP_7: case GDK_KEY_KP_Home: digit = 7; break; - case GDK_KEY_KP_8: case GDK_KEY_KP_Up: digit = 8; break; - case GDK_KEY_KP_9: case GDK_KEY_KP_Page_Up: digit = 9; break; - } - if (digit < 0) - inst->alt_keycode = -2; /* it's invalid */ - else { -#if defined(DEBUG) && defined(KEY_EVENT_DIAGNOSTICS) - int old_keycode = inst->alt_keycode; -#endif - if (inst->alt_keycode == -1) - inst->alt_keycode = digit; /* one-digit code */ - else - inst->alt_keycode = inst->alt_keycode * 10 + digit; - inst->alt_digits++; -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - Alt+numberpad digit %d added to keycode %d" - " gives %d\n", digit, old_keycode, inst->alt_keycode); -#endif - /* Having used this digit, we now do nothing more with it. */ - goto done; - } - } - - if (event->keyval == GDK_KEY_greater && - (event->state & GDK_CONTROL_MASK)) { -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - Ctrl->: increase font size\n"); -#endif - change_font_size(inst, +1); - return true; - } - if (event->keyval == GDK_KEY_less && - (event->state & GDK_CONTROL_MASK)) { -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - Ctrl-<: increase font size\n"); -#endif - change_font_size(inst, -1); - return true; - } - - /* - * Shift-PgUp and Shift-PgDn don't even generate keystrokes - * at all. - */ - if (event->keyval == GDK_KEY_Page_Up && - ((event->state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK)) == - (GDK_CONTROL_MASK | GDK_SHIFT_MASK))) { -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - Ctrl-Shift-PgUp scroll\n"); -#endif - term_scroll(inst->term, 1, 0); - return true; - } - if (event->keyval == GDK_KEY_Page_Up && - (event->state & GDK_SHIFT_MASK)) { -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - Shift-PgUp scroll\n"); -#endif - term_scroll(inst->term, 0, -inst->height/2); - return true; - } - if (event->keyval == GDK_KEY_Page_Up && - (event->state & GDK_CONTROL_MASK)) { -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - Ctrl-PgUp scroll\n"); -#endif - term_scroll(inst->term, 0, -1); - return true; - } - if (event->keyval == GDK_KEY_Page_Down && - ((event->state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK)) == - (GDK_CONTROL_MASK | GDK_SHIFT_MASK))) { -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - Ctrl-shift-PgDn scroll\n"); -#endif - term_scroll(inst->term, -1, 0); - return true; - } - if (event->keyval == GDK_KEY_Page_Down && - (event->state & GDK_SHIFT_MASK)) { -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - Shift-PgDn scroll\n"); -#endif - term_scroll(inst->term, 0, +inst->height/2); - return true; - } - if (event->keyval == GDK_KEY_Page_Down && - (event->state & GDK_CONTROL_MASK)) { -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - Ctrl-PgDn scroll\n"); -#endif - term_scroll(inst->term, 0, +1); - return true; - } - - /* - * Neither do Shift-Ins or Ctrl-Ins (if enabled). - */ - if (event->keyval == GDK_KEY_Insert && - (event->state & GDK_SHIFT_MASK)) { - int cfgval = conf_get_int(inst->conf, CONF_ctrlshiftins); - - switch (cfgval) { - case CLIPUI_IMPLICIT: -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - Shift-Insert: paste from PRIMARY\n"); -#endif - term_request_paste(inst->term, CLIP_PRIMARY); - return true; - case CLIPUI_EXPLICIT: -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - Shift-Insert: paste from CLIPBOARD\n"); -#endif - term_request_paste(inst->term, CLIP_CLIPBOARD); - return true; - case CLIPUI_CUSTOM: -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - Shift-Insert: paste from custom clipboard\n"); -#endif - term_request_paste(inst->term, inst->clipboard_ctrlshiftins); - return true; - default: -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - Shift-Insert: no paste action\n"); -#endif - break; - } - } - if (event->keyval == GDK_KEY_Insert && - (event->state & GDK_CONTROL_MASK)) { - static const int clips_clipboard[] = { CLIP_CLIPBOARD }; - int cfgval = conf_get_int(inst->conf, CONF_ctrlshiftins); - - switch (cfgval) { - case CLIPUI_IMPLICIT: - /* do nothing; re-copy to PRIMARY is not needed */ -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - Ctrl-Insert: non-copy to PRIMARY\n"); -#endif - return true; - case CLIPUI_EXPLICIT: -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - Ctrl-Insert: copy to CLIPBOARD\n"); -#endif - term_request_copy(inst->term, - clips_clipboard, lenof(clips_clipboard)); - return true; - case CLIPUI_CUSTOM: -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - Ctrl-Insert: copy to custom clipboard\n"); -#endif - term_request_copy(inst->term, - &inst->clipboard_ctrlshiftins, 1); - return true; - default: -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - Ctrl-Insert: no copy action\n"); -#endif - break; - } - } - - /* - * Another pair of copy-paste keys. - */ - if ((event->state & GDK_SHIFT_MASK) && - (event->state & GDK_CONTROL_MASK) && - (event->keyval == GDK_KEY_C || event->keyval == GDK_KEY_c || - event->keyval == GDK_KEY_V || event->keyval == GDK_KEY_v)) { - int cfgval = conf_get_int(inst->conf, CONF_ctrlshiftcv); - bool paste = (event->keyval == GDK_KEY_V || - event->keyval == GDK_KEY_v); - - switch (cfgval) { - case CLIPUI_IMPLICIT: - if (paste) { -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - Ctrl-Shift-V: paste from PRIMARY\n"); -#endif - term_request_paste(inst->term, CLIP_PRIMARY); - } else { -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - Ctrl-Shift-C: non-copy to PRIMARY\n"); -#endif - } - return true; - case CLIPUI_EXPLICIT: - if (paste) { -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - Ctrl-Shift-V: paste from CLIPBOARD\n"); -#endif - term_request_paste(inst->term, CLIP_CLIPBOARD); - } else { - static const int clips[] = { CLIP_CLIPBOARD }; -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - Ctrl-Shift-C: copy to CLIPBOARD\n"); -#endif - term_request_copy(inst->term, clips, lenof(clips)); - } - return true; - case CLIPUI_CUSTOM: - if (paste) { -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - Ctrl-Shift-V: paste from custom clipboard\n"); -#endif - term_request_paste(inst->term, - inst->clipboard_ctrlshiftcv); - } else { -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - Ctrl-Shift-C: copy to custom clipboard\n"); -#endif - term_request_copy(inst->term, - &inst->clipboard_ctrlshiftcv, 1); - } - return true; - } - } - - special = false; - use_ucsoutput = false; - - /* ALT+things gives leading Escape. */ - output[0] = '\033'; -#if !GTK_CHECK_VERSION(2,0,0) - /* - * In vanilla X, and hence also GDK 1.2, the string received - * as part of a keyboard event is assumed to be in - * ISO-8859-1. (Seems woefully shortsighted in i18n terms, - * but it's true: see the man page for XLookupString(3) for - * confirmation.) - */ - output_charset = CS_ISO8859_1; - strncpy(output+1, event_string, lenof(output)-1); -#else /* !GTK_CHECK_VERSION(2,0,0) */ - /* - * Most things can now be passed to - * gtk_im_context_filter_keypress without breaking anything - * below this point. An exception is the numeric keypad if - * we're in Nethack or application mode: the IM will eat - * numeric keypad presses if Num Lock is on, but we don't want - * it to. - */ - bool numeric = false; - bool nethack_mode = conf_get_bool(inst->conf, CONF_nethack_keypad); - bool app_keypad_mode = (inst->term->app_keypad_keys && - !conf_get_bool(inst->conf, CONF_no_applic_k)); - - switch (event->keyval) { - case GDK_KEY_Num_Lock: num_keypad_key = 'G'; break; - case GDK_KEY_KP_Divide: num_keypad_key = '/'; break; - case GDK_KEY_KP_Multiply: num_keypad_key = '*'; break; - case GDK_KEY_KP_Subtract: num_keypad_key = '-'; break; - case GDK_KEY_KP_Add: num_keypad_key = '+'; break; - case GDK_KEY_KP_Enter: num_keypad_key = '\r'; break; - case GDK_KEY_KP_0: num_keypad_key = '0'; numeric = true; break; - case GDK_KEY_KP_Insert: num_keypad_key = '0'; break; - case GDK_KEY_KP_1: num_keypad_key = '1'; numeric = true; break; - case GDK_KEY_KP_End: num_keypad_key = '1'; break; - case GDK_KEY_KP_2: num_keypad_key = '2'; numeric = true; break; - case GDK_KEY_KP_Down: num_keypad_key = '2'; break; - case GDK_KEY_KP_3: num_keypad_key = '3'; numeric = true; break; - case GDK_KEY_KP_Page_Down: num_keypad_key = '3'; break; - case GDK_KEY_KP_4: num_keypad_key = '4'; numeric = true; break; - case GDK_KEY_KP_Left: num_keypad_key = '4'; break; - case GDK_KEY_KP_5: num_keypad_key = '5'; numeric = true; break; - case GDK_KEY_KP_Begin: num_keypad_key = '5'; break; - case GDK_KEY_KP_6: num_keypad_key = '6'; numeric = true; break; - case GDK_KEY_KP_Right: num_keypad_key = '6'; break; - case GDK_KEY_KP_7: num_keypad_key = '7'; numeric = true; break; - case GDK_KEY_KP_Home: num_keypad_key = '7'; break; - case GDK_KEY_KP_8: num_keypad_key = '8'; numeric = true; break; - case GDK_KEY_KP_Up: num_keypad_key = '8'; break; - case GDK_KEY_KP_9: num_keypad_key = '9'; numeric = true; break; - case GDK_KEY_KP_Page_Up: num_keypad_key = '9'; break; - case GDK_KEY_KP_Decimal: num_keypad_key = '.'; numeric = true; break; - case GDK_KEY_KP_Delete: num_keypad_key = '.'; break; - } - if ((app_keypad_mode && num_keypad_key && - (numeric || inst->term->funky_type != FUNKY_XTERM)) || - (nethack_mode && num_keypad_key >= '1' && num_keypad_key <= '9')) { - /* In these modes, we override the keypad handling: - * regardless of Num Lock, the keys are handled by - * format_numeric_keypad_key below. */ - force_format_numeric_keypad = true; - } else { - bool try_filter = true; - -#ifdef META_MANUAL_MASK - if (event->state & META_MANUAL_MASK & inst->meta_mod_mask) { - /* - * If this key event had a Meta modifier bit set which - * is also in META_MANUAL_MASK, that means passing - * such an event to the GtkIMContext will be unhelpful - * (it will eat the keystroke and turn it into - * something not what we wanted). - */ -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - Meta modifier requiring manual intervention, " - "suppressing IM filtering\n"); -#endif - try_filter = false; - } -#endif - - if (try_filter) { -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - general key press, passing to IM\n"); -#endif - if (gtk_im_context_filter_keypress(inst->imc, event)) { -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - key press accepted by IM\n"); -#endif - return true; - } else { -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - key press not accepted by IM\n"); -#endif - } - } - } - - /* - * GDK 2.0 arranges to have done some translation for us: in - * GDK 2.0, event->string is encoded in the current locale. - * - * So we use the standard C library function mbstowcs() to - * convert from the current locale into Unicode; from there - * we can convert to whatever PuTTY is currently working in. - * (In fact I convert straight back to UTF-8 from - * wide-character Unicode, for the sake of simplicity: that - * way we can still use exactly the same code to manipulate - * the string, such as prefixing ESC.) - */ - output_charset = CS_UTF8; - { - wchar_t widedata[32]; - const wchar_t *wp; - int wlen; - int ulen; - - wlen = mb_to_wc(DEFAULT_CODEPAGE, 0, - event_string, strlen(event_string), - widedata, lenof(widedata)-1); - -#ifdef KEY_EVENT_DIAGNOSTICS - { - char *string_string = dupstr(""); - int i; - - for (i = 0; i < wlen; i++) { - char *old = string_string; - string_string = dupprintf("%s%s%04x", string_string, - string_string[0] ? " " : "", - (unsigned)widedata[i]); - sfree(old); - } - debug(" - string translated into Unicode = [%s]\n", - string_string); - sfree(string_string); - } -#endif - - wp = widedata; - ulen = charset_from_unicode(&wp, &wlen, output+1, lenof(output)-2, - CS_UTF8, NULL, NULL, 0); - -#ifdef KEY_EVENT_DIAGNOSTICS - { - char *string_string = dupstr(""); - int i; - - for (i = 0; i < ulen; i++) { - char *old = string_string; - string_string = dupprintf("%s%s%02x", string_string, - string_string[0] ? " " : "", - (unsigned)output[i+1] & 0xFF); - sfree(old); - } - debug(" - string translated into UTF-8 = [%s]\n", - string_string); - sfree(string_string); - } -#endif - - output[1+ulen] = '\0'; - } -#endif /* !GTK_CHECK_VERSION(2,0,0) */ - - if (!output[1] && - (ucsval = keysym_to_unicode(event->keyval)) >= 0) { - ucsoutput[0] = '\033'; - ucsoutput[1] = ucsval; -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - keysym_to_unicode gave %04x\n", - (unsigned)ucsoutput[1]); -#endif - use_ucsoutput = true; - end = 2; - } else { - output[lenof(output)-1] = '\0'; - end = strlen(output); - } - if (event->state & inst->meta_mod_mask) { - start = 0; - if (end == 1) end = 0; - -#ifdef META_MANUAL_MASK - if (event->state & META_MANUAL_MASK) { - /* - * Key events which have a META_MANUAL_MASK meta bit - * set may have a keyval reflecting that, e.g. on OS X - * the Option key acts as an AltGr-like modifier and - * causes different Unicode characters to be output. - * - * To work around this, we clear the dangerous - * modifier bit and retranslate from the hardware - * keycode as if the key had been pressed without that - * modifier. Then we prefix Esc to *that*. - */ - guint new_keyval; - GdkModifierType consumed; - if (gdk_keymap_translate_keyboard_state - (gdk_keymap_get_for_display(gdk_display_get_default()), - event->hardware_keycode, event->state & ~META_MANUAL_MASK, - 0, &new_keyval, NULL, NULL, &consumed)) { - ucsoutput[0] = '\033'; - ucsoutput[1] = gdk_keyval_to_unicode(new_keyval); -#ifdef KEY_EVENT_DIAGNOSTICS - { - char *keyval_name = dup_keyval_name(new_keyval); - debug(" - retranslation for manual Meta: " - "new keyval = %s, Unicode = %04x\n", - keyval_name, (unsigned)ucsoutput[1]); - sfree(keyval_name); - } -#endif - use_ucsoutput = true; - end = 2; - } - } -#endif - } else - start = 1; - - /* Control-` is the same as Control-\ (unless gtk has a better idea) */ - if (!output[1] && event->keyval == '`' && - (event->state & GDK_CONTROL_MASK)) { -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - Ctrl-` special case, translating as 1c\n"); -#endif - output[1] = '\x1C'; - use_ucsoutput = false; - end = 2; - } - - /* Some GTK backends (e.g. Quartz) do not change event->string - * in response to the Control modifier. So we do it ourselves - * here, if it's not already happened. - * - * The translations below are in line with X11 policy as far - * as I know. */ - if ((event->state & GDK_CONTROL_MASK) && end == 2) { - int orig = use_ucsoutput ? ucsoutput[1] : output[1]; - int new = orig; - - if (new >= '3' && new <= '7') { - /* ^3,...,^7 map to 0x1B,...,0x1F */ - new += '\x1B' - '3'; - } else if (new == '2' || new == ' ') { - /* ^2 and ^Space are both ^@, i.e. \0 */ - new = '\0'; - } else if (new == '8') { - /* ^8 is DEL */ - new = '\x7F'; - } else if (new == '/') { - /* ^/ is the same as ^_ */ - new = '\x1F'; - } else if (new >= 0x40 && new < 0x7F) { - /* Everything anywhere near the alphabetics just gets - * masked. */ - new &= 0x1F; - } - /* Anything else, e.g. '0', is unchanged. */ - - if (orig == new) { -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - manual Ctrl key handling did nothing\n"); -#endif - } else { -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - manual Ctrl key handling: %02x -> %02x\n", - (unsigned)orig, (unsigned)new); -#endif - output[1] = new; - use_ucsoutput = false; - } - } - - /* Control-Break sends a Break special to the backend */ - if (event->keyval == GDK_KEY_Break && - (event->state & GDK_CONTROL_MASK)) { -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - Ctrl-Break special case, sending SS_BRK\n"); -#endif - if (inst->backend) - backend_special(inst->backend, SS_BRK, 0); - return true; - } - - /* We handle Return ourselves, because it needs to be flagged as - * special to ldisc. */ - if (event->keyval == GDK_KEY_Return) { - end = return_key(inst, output, &special); - use_ucsoutput = false; - } - - /* Control-2, Control-Space and Control-@ are NUL */ - if (!output[1] && - (event->keyval == ' ' || event->keyval == '2' || - event->keyval == '@') && - (event->state & (GDK_SHIFT_MASK | - GDK_CONTROL_MASK)) == GDK_CONTROL_MASK) { -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - Ctrl-{space,2,@} special case, translating as 00\n"); -#endif - output[1] = '\0'; - use_ucsoutput = false; - end = 2; - } - - /* Control-Shift-Space is 160 (ISO8859 nonbreaking space) */ - if (!output[1] && event->keyval == ' ' && - (event->state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK)) == - (GDK_SHIFT_MASK | GDK_CONTROL_MASK)) { -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - Ctrl-Shift-space special case, translating as 00a0\n"); -#endif - output[1] = '\240'; - output_charset = CS_ISO8859_1; - use_ucsoutput = false; - end = 2; - } - - /* We don't let GTK tell us what Backspace is! We know better. */ - if (event->keyval == GDK_KEY_BackSpace && - !(event->state & GDK_SHIFT_MASK)) { - output[1] = conf_get_bool(inst->conf, CONF_bksp_is_delete) ? - '\x7F' : '\x08'; -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - Backspace, translating as %02x\n", - (unsigned)output[1]); -#endif - use_ucsoutput = false; - end = 2; - special = true; - } - /* For Shift Backspace, do opposite of what is configured. */ - if (event->keyval == GDK_KEY_BackSpace && - (event->state & GDK_SHIFT_MASK)) { - output[1] = conf_get_bool(inst->conf, CONF_bksp_is_delete) ? - '\x08' : '\x7F'; -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - Shift-Backspace, translating as %02x\n", - (unsigned)output[1]); -#endif - use_ucsoutput = false; - end = 2; - special = true; - } - - /* Shift-Tab is ESC [ Z */ - if (event->keyval == GDK_KEY_ISO_Left_Tab || - (event->keyval == GDK_KEY_Tab && - (event->state & GDK_SHIFT_MASK))) { -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - Shift-Tab, translating as ESC [ Z\n"); -#endif - end = 1 + sprintf(output+1, "\033[Z"); - use_ucsoutput = false; - } - /* And normal Tab is Tab, if the keymap hasn't already told us. - * (Curiously, at least one version of the MacOS 10.5 X server - * doesn't translate Tab for us. */ - if (event->keyval == GDK_KEY_Tab && end <= 1) { -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - Tab, translating as 09\n"); -#endif - output[1] = '\t'; - end = 2; - } - - if (num_keypad_key && force_format_numeric_keypad) { - end = 1 + format_numeric_keypad_key( - output+1, inst->term, num_keypad_key, - event->state & GDK_SHIFT_MASK, - event->state & GDK_CONTROL_MASK); -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - numeric keypad key"); -#endif - use_ucsoutput = false; - goto done; - } - - switch (event->keyval) { - int fkey_number; - case GDK_KEY_F1: fkey_number = 1; goto numbered_function_key; - case GDK_KEY_F2: fkey_number = 2; goto numbered_function_key; - case GDK_KEY_F3: fkey_number = 3; goto numbered_function_key; - case GDK_KEY_F4: fkey_number = 4; goto numbered_function_key; - case GDK_KEY_F5: fkey_number = 5; goto numbered_function_key; - case GDK_KEY_F6: fkey_number = 6; goto numbered_function_key; - case GDK_KEY_F7: fkey_number = 7; goto numbered_function_key; - case GDK_KEY_F8: fkey_number = 8; goto numbered_function_key; - case GDK_KEY_F9: fkey_number = 9; goto numbered_function_key; - case GDK_KEY_F10: fkey_number = 10; goto numbered_function_key; - case GDK_KEY_F11: fkey_number = 11; goto numbered_function_key; - case GDK_KEY_F12: fkey_number = 12; goto numbered_function_key; - case GDK_KEY_F13: fkey_number = 13; goto numbered_function_key; - case GDK_KEY_F14: fkey_number = 14; goto numbered_function_key; - case GDK_KEY_F15: fkey_number = 15; goto numbered_function_key; - case GDK_KEY_F16: fkey_number = 16; goto numbered_function_key; - case GDK_KEY_F17: fkey_number = 17; goto numbered_function_key; - case GDK_KEY_F18: fkey_number = 18; goto numbered_function_key; - case GDK_KEY_F19: fkey_number = 19; goto numbered_function_key; - case GDK_KEY_F20: fkey_number = 20; goto numbered_function_key; - numbered_function_key: - end = 1 + format_function_key(output+1, inst->term, fkey_number, - event->state & GDK_SHIFT_MASK, - event->state & GDK_CONTROL_MASK); -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - function key F%d", fkey_number); -#endif - use_ucsoutput = false; - goto done; - - SmallKeypadKey sk_key; - case GDK_KEY_Home: case GDK_KEY_KP_Home: - sk_key = SKK_HOME; goto small_keypad_key; - case GDK_KEY_Insert: case GDK_KEY_KP_Insert: - sk_key = SKK_INSERT; goto small_keypad_key; - case GDK_KEY_Delete: case GDK_KEY_KP_Delete: - sk_key = SKK_DELETE; goto small_keypad_key; - case GDK_KEY_End: case GDK_KEY_KP_End: - sk_key = SKK_END; goto small_keypad_key; - case GDK_KEY_Page_Up: case GDK_KEY_KP_Page_Up: - sk_key = SKK_PGUP; goto small_keypad_key; - case GDK_KEY_Page_Down: case GDK_KEY_KP_Page_Down: - sk_key = SKK_PGDN; goto small_keypad_key; - small_keypad_key: - /* These keys don't generate terminal input with Ctrl */ - if (event->state & GDK_CONTROL_MASK) - break; - - end = 1 + format_small_keypad_key(output+1, inst->term, sk_key); -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - small keypad key"); -#endif - use_ucsoutput = false; - goto done; - - int xkey; - case GDK_KEY_Up: case GDK_KEY_KP_Up: - xkey = 'A'; goto arrow_key; - case GDK_KEY_Down: case GDK_KEY_KP_Down: - xkey = 'B'; goto arrow_key; - case GDK_KEY_Right: case GDK_KEY_KP_Right: - xkey = 'C'; goto arrow_key; - case GDK_KEY_Left: case GDK_KEY_KP_Left: - xkey = 'D'; goto arrow_key; - case GDK_KEY_Begin: case GDK_KEY_KP_Begin: - xkey = 'G'; goto arrow_key; - arrow_key: - end = 1 + format_arrow_key(output+1, inst->term, xkey, - event->state & GDK_CONTROL_MASK); -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - arrow key"); -#endif - use_ucsoutput = false; - goto done; - } - - if (num_keypad_key) { - end = 1 + format_numeric_keypad_key( - output+1, inst->term, num_keypad_key, - event->state & GDK_SHIFT_MASK, - event->state & GDK_CONTROL_MASK); -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - numeric keypad key"); -#endif - - if (end == 1 && num_keypad_key == '\r') { - /* Keypad Enter, lacking any other translation, - * becomes the same special Return code as normal - * Return. */ - end = return_key(inst, output, &special); - use_ucsoutput = false; - } - - use_ucsoutput = false; - goto done; - } - - goto done; - } - - done: - - if (end-start > 0) { - if (special) { -#ifdef KEY_EVENT_DIAGNOSTICS - char *string_string = dupstr(""); - int i; - - for (i = start; i < end; i++) { - char *old = string_string; - string_string = dupprintf("%s%s%02x", string_string, - string_string[0] ? " " : "", - (unsigned)output[i] & 0xFF); - sfree(old); - } - debug(" - final output, special, generic encoding = [%s]\n", - string_string); - sfree(string_string); -#endif - /* - * For special control characters, the character set - * should never matter. - */ - output[end] = '\0'; /* NUL-terminate */ - generated_something = true; - term_keyinput(inst->term, -1, output+start, -2); - } else if (!inst->direct_to_font) { - if (!use_ucsoutput) { -#ifdef KEY_EVENT_DIAGNOSTICS - char *string_string = dupstr(""); - int i; - - for (i = start; i < end; i++) { - char *old = string_string; - string_string = dupprintf("%s%s%02x", string_string, - string_string[0] ? " " : "", - (unsigned)output[i] & 0xFF); - sfree(old); - } - debug(" - final output in %s = [%s]\n", - charset_to_localenc(output_charset), string_string); - sfree(string_string); -#endif - generated_something = true; - term_keyinput(inst->term, output_charset, - output+start, end-start); - } else { -#ifdef KEY_EVENT_DIAGNOSTICS - char *string_string = dupstr(""); - int i; - - for (i = start; i < end; i++) { - char *old = string_string; - string_string = dupprintf("%s%s%04x", string_string, - string_string[0] ? " " : "", - (unsigned)ucsoutput[i]); - sfree(old); - } - debug(" - final output in Unicode = [%s]\n", - string_string); - sfree(string_string); -#endif - - /* - * We generated our own Unicode key data from the - * keysym, so use that instead. - */ - generated_something = true; - term_keyinputw(inst->term, ucsoutput+start, end-start); - } - } else { - /* - * In direct-to-font mode, we just send the string - * exactly as we received it. - */ -#ifdef KEY_EVENT_DIAGNOSTICS - char *string_string = dupstr(""); - int i; - - for (i = start; i < end; i++) { - char *old = string_string; - string_string = dupprintf("%s%s%02x", string_string, - string_string[0] ? " " : "", - (unsigned)output[i] & 0xFF); - sfree(old); - } - debug(" - final output in direct-to-font encoding = [%s]\n", - string_string); - sfree(string_string); -#endif - generated_something = true; - term_keyinput(inst->term, -1, output+start, end-start); - } - - show_mouseptr(inst, false); - } - - if (generated_something) - key_pressed(inst); - return true; -} - -#if GTK_CHECK_VERSION(2,0,0) -void input_method_commit_event(GtkIMContext *imc, gchar *str, gpointer data) -{ - GtkFrontend *inst = (GtkFrontend *)data; - -#ifdef KEY_EVENT_DIAGNOSTICS - char *string_string = dupstr(""); - int i; - - for (i = 0; str[i]; i++) { - char *old = string_string; - string_string = dupprintf("%s%s%02x", string_string, - string_string[0] ? " " : "", - (unsigned)str[i] & 0xFF); - sfree(old); - } - debug(" - IM commit event in UTF-8 = [%s]\n", string_string); - sfree(string_string); -#endif - - term_keyinput(inst->term, CS_UTF8, str, strlen(str)); - show_mouseptr(inst, false); - key_pressed(inst); -} -#endif - -#define SCROLL_INCREMENT_LINES 5 - -#if GTK_CHECK_VERSION(3,4,0) -gboolean scroll_internal(GtkFrontend *inst, gdouble delta, guint state, - gdouble ex, gdouble ey) -{ - int x, y; - bool shift, ctrl, alt, raw_mouse_mode; - - show_mouseptr(inst, true); - - shift = state & GDK_SHIFT_MASK; - ctrl = state & GDK_CONTROL_MASK; - alt = state & inst->meta_mod_mask; - - x = (ex - inst->window_border) / inst->font_width; - y = (ey - inst->window_border) / inst->font_height; - - raw_mouse_mode = (inst->send_raw_mouse && - !(shift && conf_get_bool(inst->conf, - CONF_mouse_override))); - - inst->cumulative_scroll += delta * SCROLL_INCREMENT_LINES; - - if (!raw_mouse_mode) { - int scroll_lines = (int)inst->cumulative_scroll; /* rounds toward 0 */ - if (scroll_lines) { - term_scroll(inst->term, 0, scroll_lines); - inst->cumulative_scroll -= scroll_lines; - } - return true; - } else { - int scroll_events = (int)(inst->cumulative_scroll / - SCROLL_INCREMENT_LINES); - if (scroll_events) { - int button; - - inst->cumulative_scroll -= scroll_events * SCROLL_INCREMENT_LINES; - - if (scroll_events > 0) { - button = MBT_WHEEL_DOWN; - } else { - button = MBT_WHEEL_UP; - scroll_events = -scroll_events; - } - - while (scroll_events-- > 0) { - term_mouse(inst->term, button, translate_button(button), - MA_CLICK, x, y, shift, ctrl, alt); - } - } - return true; - } -} -#endif - -static gboolean button_internal(GtkFrontend *inst, GdkEventButton *event) -{ - bool shift, ctrl, alt, raw_mouse_mode; - int x, y, button, act; - - /* Remember the timestamp. */ - inst->input_event_time = event->time; - - noise_ultralight(NOISE_SOURCE_MOUSEBUTTON, event->button); - - show_mouseptr(inst, true); - - shift = event->state & GDK_SHIFT_MASK; - ctrl = event->state & GDK_CONTROL_MASK; - alt = event->state & inst->meta_mod_mask; - - raw_mouse_mode = (inst->send_raw_mouse && - !(shift && conf_get_bool(inst->conf, - CONF_mouse_override))); - - if (!raw_mouse_mode) { - if (event->button == 4 && event->type == GDK_BUTTON_PRESS) { - term_scroll(inst->term, 0, -SCROLL_INCREMENT_LINES); - return true; - } - if (event->button == 5 && event->type == GDK_BUTTON_PRESS) { - term_scroll(inst->term, 0, +SCROLL_INCREMENT_LINES); - return true; - } - } - - if (event->button == 3 && ctrl) { -#if GTK_CHECK_VERSION(3,22,0) - gtk_menu_popup_at_pointer(GTK_MENU(inst->menu), (GdkEvent *)event); -#else - gtk_menu_popup(GTK_MENU(inst->menu), NULL, NULL, NULL, NULL, - event->button, event->time); -#endif - return true; - } - - if (event->button == 1) - button = MBT_LEFT; - else if (event->button == 2) - button = MBT_MIDDLE; - else if (event->button == 3) - button = MBT_RIGHT; - else if (event->button == 4) - button = MBT_WHEEL_UP; - else if (event->button == 5) - button = MBT_WHEEL_DOWN; - else - return false; /* don't even know what button! */ - - switch (event->type) { - case GDK_BUTTON_PRESS: act = MA_CLICK; break; - case GDK_BUTTON_RELEASE: act = MA_RELEASE; break; - case GDK_2BUTTON_PRESS: act = MA_2CLK; break; - case GDK_3BUTTON_PRESS: act = MA_3CLK; break; - default: return false; /* don't know this event type */ - } - - if (raw_mouse_mode && act != MA_CLICK && act != MA_RELEASE) - return true; /* we ignore these in raw mouse mode */ - - x = (event->x - inst->window_border) / inst->font_width; - y = (event->y - inst->window_border) / inst->font_height; - - term_mouse(inst->term, button, translate_button(button), act, - x, y, shift, ctrl, alt); - - return true; -} - -gboolean button_event(GtkWidget *widget, GdkEventButton *event, gpointer data) -{ - GtkFrontend *inst = (GtkFrontend *)data; - return button_internal(inst, event); -} - -#if GTK_CHECK_VERSION(2,0,0) -/* - * In GTK 2, mouse wheel events have become a new type of event. - * This handler translates them back into button-4 and button-5 - * presses so that I don't have to change my old code too much :-) - */ -gboolean scroll_event(GtkWidget *widget, GdkEventScroll *event, gpointer data) -{ - GtkFrontend *inst = (GtkFrontend *)data; - GdkScrollDirection dir; - -#if GTK_CHECK_VERSION(3,4,0) - gdouble dx, dy; - if (gdk_event_get_scroll_deltas((GdkEvent *)event, &dx, &dy)) { - return scroll_internal(inst, dy, event->state, event->x, event->y); - } else if (!gdk_event_get_scroll_direction((GdkEvent *)event, &dir)) { - return false; - } -#else - dir = event->direction; -#endif - - guint button; - GdkEventButton *event_button; - gboolean ret; - - if (dir == GDK_SCROLL_UP) - button = 4; - else if (dir == GDK_SCROLL_DOWN) - button = 5; - else - return false; - - event_button = (GdkEventButton *)gdk_event_new(GDK_BUTTON_PRESS); - event_button->window = g_object_ref(event->window); - event_button->send_event = event->send_event; - event_button->time = event->time; - event_button->x = event->x; - event_button->y = event->y; - event_button->axes = NULL; - event_button->state = event->state; - event_button->button = button; - event_button->device = g_object_ref(event->device); - event_button->x_root = event->x_root; - event_button->y_root = event->y_root; - ret = button_internal(inst, event_button); - gdk_event_free((GdkEvent *)event_button); - return ret; -} -#endif - -gint motion_event(GtkWidget *widget, GdkEventMotion *event, gpointer data) -{ - GtkFrontend *inst = (GtkFrontend *)data; - bool shift, ctrl, alt; - int x, y, button; - - /* Remember the timestamp. */ - inst->input_event_time = event->time; - - noise_ultralight(NOISE_SOURCE_MOUSEPOS, - ((uint32_t)event->x << 16) | (uint32_t)event->y); - - show_mouseptr(inst, true); - - shift = event->state & GDK_SHIFT_MASK; - ctrl = event->state & GDK_CONTROL_MASK; - alt = event->state & inst->meta_mod_mask; - if (event->state & GDK_BUTTON1_MASK) - button = MBT_LEFT; - else if (event->state & GDK_BUTTON2_MASK) - button = MBT_MIDDLE; - else if (event->state & GDK_BUTTON3_MASK) - button = MBT_RIGHT; - else - return false; /* don't even know what button! */ - - x = (event->x - inst->window_border) / inst->font_width; - y = (event->y - inst->window_border) / inst->font_height; - - term_mouse(inst->term, button, translate_button(button), MA_DRAG, - x, y, shift, ctrl, alt); - - return true; -} - -static void key_pressed(GtkFrontend *inst) -{ - /* - * If our child process has exited but not closed, terminate on - * any keypress. - * - * This is a UI feature specific to GTK PuTTY, because GTK PuTTY - * will (at least sometimes) be running under X, and under X the - * window manager is sometimes absent (very occasionally on - * purpose, more usually temporarily because it's crashed). So - * it's useful to have a way to close an application window - * without depending on protocols like WM_DELETE_WINDOW that are - * typically generated by the WM (e.g. in response to a close - * button in the window frame). - */ - if (inst->exited) - gtk_widget_destroy(inst->window); -} - -static void exit_callback(void *vctx) -{ - GtkFrontend *inst = (GtkFrontend *)vctx; - int exitcode, close_on_exit; - - if (!inst->exited && - (exitcode = backend_exitcode(inst->backend)) >= 0) { - destroy_inst_connection(inst); - - close_on_exit = conf_get_int(inst->conf, CONF_close_on_exit); - if (close_on_exit == FORCE_ON || - (close_on_exit == AUTO && exitcode == 0)) { - gtk_widget_destroy(inst->window); - } - } -} - -static void gtk_seat_notify_remote_exit(Seat *seat) -{ - GtkFrontend *inst = container_of(seat, GtkFrontend, seat); - queue_toplevel_callback(exit_callback, inst); -} - -static void destroy_inst_connection(GtkFrontend *inst) -{ - inst->exited = true; - if (inst->ldisc) { - ldisc_free(inst->ldisc); - inst->ldisc = NULL; - } - if (inst->backend) { - backend_free(inst->backend); - inst->backend = NULL; - } - if (inst->term) - term_provide_backend(inst->term, NULL); - if (inst->menu) { - seat_update_specials_menu(&inst->seat); - gtk_widget_set_sensitive(inst->restartitem, true); - } -} - -static void delete_inst(GtkFrontend *inst) -{ - int dialog_slot; - for (dialog_slot = 0; dialog_slot < DIALOG_SLOT_LIMIT; dialog_slot++) { - if (inst->dialogs[dialog_slot]) { - gtk_widget_destroy(inst->dialogs[dialog_slot]); - inst->dialogs[dialog_slot] = NULL; - } - } - if (inst->window) { - gtk_widget_destroy(inst->window); - inst->window = NULL; - } - if (inst->menu) { - gtk_widget_destroy(inst->menu); - inst->menu = NULL; - } - destroy_inst_connection(inst); - if (inst->term) { - term_free(inst->term); - inst->term = NULL; - } - if (inst->conf) { - conf_free(inst->conf); - inst->conf = NULL; - } - if (inst->logctx) { - log_free(inst->logctx); - inst->logctx = NULL; - } -#if GTK_CHECK_VERSION(2,0,0) - if (inst->trust_sigil_pb) { - g_object_unref(G_OBJECT(inst->trust_sigil_pb)); - inst->trust_sigil_pb = NULL; - } -#else - if (inst->trust_sigil_pm) { - gdk_pixmap_unref(inst->trust_sigil_pm); - inst->trust_sigil_pm = NULL; - } -#endif - -#ifdef JUST_USE_GTK_CLIPBOARD_UTF8 - /* - * Clear up any in-flight clipboard_data_instances. We can't - * actually _free_ them, but we detach them from the inst that's - * about to be destroyed. - */ - while (inst->cdi_headtail.next != &inst->cdi_headtail) { - struct clipboard_data_instance *cdi = inst->cdi_headtail.next; - cdi->state = NULL; - cdi->next->prev = cdi->prev; - cdi->prev->next = cdi->next; - cdi->next = cdi->prev = cdi; - } -#endif - - /* - * Delete any top-level callbacks associated with inst, which - * would otherwise become stale-pointer dereferences waiting to - * happen. We do this last, because some of the above cleanups - * (notably shutting down the backend) might themelves queue such - * callbacks, so we need to make sure they don't do that _after_ - * we're supposed to have cleaned everything up. - */ - delete_callbacks_for_context(inst); - - eventlogstuff_free(inst->eventlogstuff); - - sfree(inst); -} - -void destroy(GtkWidget *widget, gpointer data) -{ - GtkFrontend *inst = (GtkFrontend *)data; - inst->window = NULL; - delete_inst(inst); - session_window_closed(); -} - -gint focus_event(GtkWidget *widget, GdkEventFocus *event, gpointer data) -{ - GtkFrontend *inst = (GtkFrontend *)data; - term_set_focus(inst->term, event->in); - term_update(inst->term); - show_mouseptr(inst, true); - return false; -} - -static void gtk_seat_set_busy_status(Seat *seat, BusyStatus status) -{ - GtkFrontend *inst = container_of(seat, GtkFrontend, seat); - inst->busy_status = status; - update_mouseptr(inst); -} - -static void gtkwin_set_raw_mouse_mode(TermWin *tw, bool activate) -{ - GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); - inst->send_raw_mouse = activate; -} - -static void gtkwin_set_raw_mouse_mode_pointer(TermWin *tw, bool activate) -{ - GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); - inst->pointer_indicates_raw_mouse = activate; - update_mouseptr(inst); -} - -#if GTK_CHECK_VERSION(2,0,0) -static void compute_whole_window_size(GtkFrontend *inst, - int wchars, int hchars, - int *wpix, int *hpix); -#endif - -static void gtkwin_request_resize(TermWin *tw, int w, int h) -{ - GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); - -#if !GTK_CHECK_VERSION(3,0,0) - - int large_x, large_y; - int offset_x, offset_y; - int area_x, area_y; - GtkRequisition inner, outer; - - /* - * This is a heinous hack dreamed up by the gnome-terminal - * people to get around a limitation in gtk. The problem is - * that in order to set the size correctly we really need to be - * calling gtk_window_resize - but that needs to know the size - * of the _whole window_, not the drawing area. So what we do - * is to set an artificially huge size request on the drawing - * area, recompute the resulting size request on the window, - * and look at the difference between the two. That gives us - * the x and y offsets we need to translate drawing area size - * into window size for real, and then we call - * gtk_window_resize. - */ - - /* - * We start by retrieving the current size of the whole window. - * Adding a bit to _that_ will give us a value we can use as a - * bogus size request which guarantees to be bigger than the - * current size of the drawing area. - */ - get_window_pixel_size(inst, &large_x, &large_y); - large_x += 32; - large_y += 32; - - gtk_widget_set_size_request(inst->area, large_x, large_y); - gtk_widget_size_request(inst->area, &inner); - gtk_widget_size_request(inst->window, &outer); - - offset_x = outer.width - inner.width; - offset_y = outer.height - inner.height; - - area_x = inst->font_width * w + 2*inst->window_border; - area_y = inst->font_height * h + 2*inst->window_border; - - /* - * Now we must set the size request on the drawing area back to - * something sensible before we commit the real resize. Best - * way to do this, I think, is to set it to what the size is - * really going to end up being. - */ - gtk_widget_set_size_request(inst->area, area_x, area_y); -#if GTK_CHECK_VERSION(2,0,0) - gtk_window_resize(GTK_WINDOW(inst->window), - area_x + offset_x, area_y + offset_y); -#else - gtk_drawing_area_size(GTK_DRAWING_AREA(inst->area), area_x, area_y); - /* - * I can no longer remember what this call to - * gtk_container_dequeue_resize_handler is for. It was - * introduced in r3092 with no comment, and the commit log - * message was uninformative. I'm _guessing_ its purpose is to - * prevent gratuitous resize processing on the window given - * that we're about to resize it anyway, but I have no idea - * why that's so incredibly vital. - * - * I've tried removing the call, and nothing seems to go - * wrong. I've backtracked to r3092 and tried removing the - * call there, and still nothing goes wrong. So I'm going to - * adopt the working hypothesis that it's superfluous; I won't - * actually remove it from the GTK 1.2 code, but I won't - * attempt to replicate its functionality in the GTK 2 code - * above. - */ - gtk_container_dequeue_resize_handler(GTK_CONTAINER(inst->window)); - gdk_window_resize(gtk_widget_get_window(inst->window), - area_x + offset_x, area_y + offset_y); -#endif - -#else /* GTK_CHECK_VERSION(3,0,0) */ - - int wp, hp; - compute_whole_window_size(inst, w, h, &wp, &hp); - gtk_window_resize(GTK_WINDOW(inst->window), wp, hp); - -#endif - -} - -#if GTK_CHECK_VERSION(3,0,0) -char *colour_to_css(const GdkColor *col) -{ - GdkRGBA rgba; - rgba.red = col->red / 65535.0; - rgba.green = col->green / 65535.0; - rgba.blue = col->blue / 65535.0; - rgba.alpha = 1.0; - return gdk_rgba_to_string(&rgba); -} -#endif - -void set_gtk_widget_background(GtkWidget *widget, const GdkColor *col) -{ -#if GTK_CHECK_VERSION(3,0,0) - GtkCssProvider *provider = gtk_css_provider_new(); - char *col_css = colour_to_css(col); - char *data = dupprintf( - "#drawing-area, #top-level { background-color: %s; }\n", col_css); - gtk_css_provider_load_from_data(provider, data, -1, NULL); - GtkStyleContext *context = gtk_widget_get_style_context(widget); - gtk_style_context_add_provider(context, GTK_STYLE_PROVIDER(provider), - GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); - free(data); - free(col_css); -#else - if (gtk_widget_get_window(widget)) { - /* For GTK1, which doesn't have a 'const' on - * gdk_window_set_background's second parameter type. */ - GdkColor col_mutable = *col; - gdk_window_set_background(gtk_widget_get_window(widget), &col_mutable); - } -#endif -} - -void set_window_background(GtkFrontend *inst) -{ - if (inst->area) - set_gtk_widget_background(GTK_WIDGET(inst->area), &inst->cols[258]); - if (inst->window) - set_gtk_widget_background(GTK_WIDGET(inst->window), &inst->cols[258]); -} - -static void gtkwin_palette_set(TermWin *tw, unsigned start, unsigned ncolours, - const rgb *colours) -{ - GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); - - assert(start <= OSC4_NCOLOURS); - assert(ncolours <= OSC4_NCOLOURS - start); - -#if !GTK_CHECK_VERSION(3,0,0) - if (!inst->colmap) { - inst->colmap = gdk_colormap_get_system(); - } else { - gdk_colormap_free_colors(inst->colmap, inst->cols, OSC4_NCOLOURS); - } -#endif - - for (unsigned i = 0; i < ncolours; i++) { - const rgb *in = &colours[i]; - GdkColor *out = &inst->cols[start + i]; - - out->red = in->r * 0x0101; - out->green = in->g * 0x0101; - out->blue = in->b * 0x0101; - } - -#if !GTK_CHECK_VERSION(3,0,0) - { - gboolean success[OSC4_NCOLOURS]; - gdk_colormap_alloc_colors(inst->colmap, inst->cols + start, - ncolours, false, true, success); - for (unsigned i = 0; i < ncolours; i++) { - if (!success[i]) - g_error("%s: couldn't allocate colour %d (#%02x%02x%02x)\n", - appname, start + i, - conf_get_int_int(inst->conf, CONF_colours, i*3+0), - conf_get_int_int(inst->conf, CONF_colours, i*3+1), - conf_get_int_int(inst->conf, CONF_colours, i*3+2)); - } - } -#endif - - if (start <= OSC4_COLOUR_bg && OSC4_COLOUR_bg < start + ncolours) { - /* Default Background has changed, so ensure that space between text - * area and window border is refreshed. */ - set_window_background(inst); - if (inst->area && gtk_widget_get_window(inst->area)) { - draw_backing_rect(inst); - gtk_widget_queue_draw(inst->area); - } - } -} - -static void gtkwin_palette_get_overrides(TermWin *tw, Terminal *term) -{ - /* GTK has no analogue of Windows's 'standard system colours', so GTK PuTTY - * has no config option to override the normally configured colours from - * it */ -} - -static struct clipboard_state *clipboard_from_atom( - GtkFrontend *inst, GdkAtom atom) -{ - int i; - - for (i = 0; i < N_CLIPBOARDS; i++) { - struct clipboard_state *state = &inst->clipstates[i]; - if (state->inst == inst && state->atom == atom) - return state; - } - - return NULL; -} - -#ifdef JUST_USE_GTK_CLIPBOARD_UTF8 - -/* ---------------------------------------------------------------------- - * Clipboard handling, using the high-level GtkClipboard interface in - * as hands-off a way as possible. We write and read the clipboard as - * UTF-8 text, and let GTK deal with converting to any other text - * formats it feels like. - */ - -void set_clipboard_atom(GtkFrontend *inst, int clipboard, GdkAtom atom) -{ - struct clipboard_state *state = &inst->clipstates[clipboard]; - - state->inst = inst; - state->clipboard = clipboard; - state->atom = atom; - - if (state->atom != GDK_NONE) { - state->gtkclipboard = gtk_clipboard_get_for_display( - gdk_display_get_default(), state->atom); - g_object_set_data(G_OBJECT(state->gtkclipboard), "user-data", state); - } else { - state->gtkclipboard = NULL; - } -} - -int init_clipboard(GtkFrontend *inst) -{ - set_clipboard_atom(inst, CLIP_PRIMARY, GDK_SELECTION_PRIMARY); - set_clipboard_atom(inst, CLIP_CLIPBOARD, clipboard_atom); - return true; -} - -static void clipboard_provide_data(GtkClipboard *clipboard, - GtkSelectionData *selection_data, - guint info, gpointer data) -{ - struct clipboard_data_instance *cdi = - (struct clipboard_data_instance *)data; - - if (cdi->state && cdi->state->current_cdi == cdi) { - gtk_selection_data_set_text(selection_data, cdi->pasteout_data_utf8, - cdi->pasteout_data_utf8_len); - } -} - -static void clipboard_clear(GtkClipboard *clipboard, gpointer data) -{ - struct clipboard_data_instance *cdi = - (struct clipboard_data_instance *)data; - - if (cdi->state && cdi->state->current_cdi == cdi) { - if (cdi->state->inst && cdi->state->inst->term) { - term_lost_clipboard_ownership(cdi->state->inst->term, - cdi->state->clipboard); - } - cdi->state->current_cdi = NULL; - } - sfree(cdi->pasteout_data_utf8); - cdi->next->prev = cdi->prev; - cdi->prev->next = cdi->next; - sfree(cdi); -} - -static void gtkwin_clip_write( - TermWin *tw, int clipboard, wchar_t *data, int *attr, - truecolour *truecolour, int len, bool must_deselect) -{ - GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); - struct clipboard_state *state = &inst->clipstates[clipboard]; - struct clipboard_data_instance *cdi; - - if (inst->direct_to_font) { - /* In this clipboard mode, we just can't paste if we're in - * direct-to-font mode. Fortunately, that shouldn't be - * important, because we'll only use this clipboard handling - * code on systems where that kind of font doesn't exist - * anyway. */ - return; - } - - if (!state->gtkclipboard) - return; - - cdi = snew(struct clipboard_data_instance); - cdi->state = state; - state->current_cdi = cdi; - cdi->pasteout_data_utf8 = snewn(len*6, char); - cdi->prev = inst->cdi_headtail.prev; - cdi->next = &inst->cdi_headtail; - cdi->next->prev = cdi; - cdi->prev->next = cdi; - { - const wchar_t *tmp = data; - int tmplen = len; - cdi->pasteout_data_utf8_len = - charset_from_unicode(&tmp, &tmplen, cdi->pasteout_data_utf8, - len*6, CS_UTF8, NULL, NULL, 0); - } - - /* - * It would be nice to just call gtk_clipboard_set_text() in place - * of all of the faffing below. Unfortunately, that won't give me - * access to the clipboard-clear event, which we use to visually - * deselect text in the terminal. - */ - { - GtkTargetList *targetlist; - GtkTargetEntry *targettable; - gint n_targets; - - targetlist = gtk_target_list_new(NULL, 0); - gtk_target_list_add_text_targets(targetlist, 0); - targettable = gtk_target_table_new_from_list(targetlist, &n_targets); - gtk_clipboard_set_with_data(state->gtkclipboard, targettable, - n_targets, clipboard_provide_data, - clipboard_clear, cdi); - gtk_target_table_free(targettable, n_targets); - gtk_target_list_unref(targetlist); - } -} - -static void clipboard_text_received(GtkClipboard *clipboard, - const gchar *text, gpointer data) -{ - GtkFrontend *inst = (GtkFrontend *)data; - wchar_t *paste; - int paste_len; - int length; - - if (!text) - return; - - length = strlen(text); - - paste = snewn(length, wchar_t); - paste_len = mb_to_wc(CS_UTF8, 0, text, length, paste, length); - - term_do_paste(inst->term, paste, paste_len); - - sfree(paste); -} - -static void gtkwin_clip_request_paste(TermWin *tw, int clipboard) -{ - GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); - struct clipboard_state *state = &inst->clipstates[clipboard]; - - if (!state->gtkclipboard) - return; - - gtk_clipboard_request_text(state->gtkclipboard, - clipboard_text_received, inst); -} - -#else /* JUST_USE_GTK_CLIPBOARD_UTF8 */ - -/* ---------------------------------------------------------------------- - * Clipboard handling for X, using the low-level gtk_selection_* - * interface, handling conversions to fiddly things like compound text - * ourselves, and storing in X cut buffers too. - * - * This version of the clipboard code has to be kept around for GTK1, - * which doesn't have the higher-level GtkClipboard interface at all. - * And since it works on GTK2 and GTK3 too and has had a good few - * years of shakedown and bug fixing, we might as well keep using it - * where it's applicable. - * - * It's _possible_ that we might be able to replicate all the - * important wrinkles of this code in GtkClipboard. (In particular, - * cut buffers or local analogue look as if they might be accessible - * via gtk_clipboard_set_can_store(), and delivering text in - * non-Unicode formats only in the direct-to-font case ought to be - * possible if we can figure out the right set of things to put in the - * GtkTargetList.) But that work can wait until there's a need for it! - */ - -#ifndef NOT_X_WINDOWS - -/* Store the data in a cut-buffer. */ -static void store_cutbuffer(GtkFrontend *inst, char *ptr, int len) -{ - if (inst->disp) { - /* ICCCM says we must rotate the buffers before storing to buffer 0. */ - XRotateBuffers(inst->disp, 1); - XStoreBytes(inst->disp, ptr, len); - } -} - -/* Retrieve data from a cut-buffer. - * Returned data needs to be freed with XFree(). - */ -static char *retrieve_cutbuffer(GtkFrontend *inst, int *nbytes) -{ - char *ptr; - if (!inst->disp) { - *nbytes = 0; - return NULL; - } - ptr = XFetchBytes(inst->disp, nbytes); - if (*nbytes <= 0 && ptr != 0) { - XFree(ptr); - ptr = 0; - } - return ptr; -} - -#endif /* NOT_X_WINDOWS */ - -static void gtkwin_clip_write( - TermWin *tw, int clipboard, wchar_t *data, int *attr, - truecolour *truecolour, int len, bool must_deselect) -{ - GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); - struct clipboard_state *state = &inst->clipstates[clipboard]; - - if (state->pasteout_data) - sfree(state->pasteout_data); - if (state->pasteout_data_ctext) - sfree(state->pasteout_data_ctext); - if (state->pasteout_data_utf8) - sfree(state->pasteout_data_utf8); - - /* - * Set up UTF-8 and compound text paste data. This only happens - * if we aren't in direct-to-font mode using the D800 hack. - */ - if (!inst->direct_to_font) { - const wchar_t *tmp = data; - int tmplen = len; -#ifndef NOT_X_WINDOWS - XTextProperty tp; - char *list[1]; -#endif - - state->pasteout_data_utf8 = snewn(len*6, char); - state->pasteout_data_utf8_len = len*6; - state->pasteout_data_utf8_len = - charset_from_unicode(&tmp, &tmplen, state->pasteout_data_utf8, - state->pasteout_data_utf8_len, - CS_UTF8, NULL, NULL, 0); - if (state->pasteout_data_utf8_len == 0) { - sfree(state->pasteout_data_utf8); - state->pasteout_data_utf8 = NULL; - } else { - state->pasteout_data_utf8 = - sresize(state->pasteout_data_utf8, - state->pasteout_data_utf8_len + 1, char); - state->pasteout_data_utf8[state->pasteout_data_utf8_len] = '\0'; - } - - /* - * Now let Xlib convert our UTF-8 data into compound text. - */ -#ifndef NOT_X_WINDOWS - list[0] = state->pasteout_data_utf8; - if (inst->disp && Xutf8TextListToTextProperty( - inst->disp, list, 1, XCompoundTextStyle, &tp) == 0) { - state->pasteout_data_ctext = snewn(tp.nitems+1, char); - memcpy(state->pasteout_data_ctext, tp.value, tp.nitems); - state->pasteout_data_ctext_len = tp.nitems; - XFree(tp.value); - } else -#endif - { - state->pasteout_data_ctext = NULL; - state->pasteout_data_ctext_len = 0; - } - } else { - state->pasteout_data_utf8 = NULL; - state->pasteout_data_utf8_len = 0; - state->pasteout_data_ctext = NULL; - state->pasteout_data_ctext_len = 0; - } - - state->pasteout_data = snewn(len*6, char); - state->pasteout_data_len = len*6; - state->pasteout_data_len = wc_to_mb(inst->ucsdata.line_codepage, 0, - data, len, state->pasteout_data, - state->pasteout_data_len, - NULL, NULL); - if (state->pasteout_data_len == 0) { - sfree(state->pasteout_data); - state->pasteout_data = NULL; - } else { - state->pasteout_data = - sresize(state->pasteout_data, state->pasteout_data_len, char); - } - -#ifndef NOT_X_WINDOWS - /* The legacy X cut buffers go with PRIMARY, not any other clipboard */ - if (state->atom == GDK_SELECTION_PRIMARY) - store_cutbuffer(inst, state->pasteout_data, state->pasteout_data_len); -#endif - - if (gtk_selection_owner_set(inst->area, state->atom, - inst->input_event_time)) { -#if GTK_CHECK_VERSION(2,0,0) - gtk_selection_clear_targets(inst->area, state->atom); -#endif - gtk_selection_add_target(inst->area, state->atom, - GDK_SELECTION_TYPE_STRING, 1); - if (state->pasteout_data_ctext) - gtk_selection_add_target(inst->area, state->atom, - compound_text_atom, 1); - if (state->pasteout_data_utf8) - gtk_selection_add_target(inst->area, state->atom, - utf8_string_atom, 1); - } - - if (must_deselect) - term_lost_clipboard_ownership(inst->term, clipboard); -} - -static void selection_get(GtkWidget *widget, GtkSelectionData *seldata, - guint info, guint time_stamp, gpointer data) -{ - GtkFrontend *inst = (GtkFrontend *)data; - GdkAtom target = gtk_selection_data_get_target(seldata); - struct clipboard_state *state = clipboard_from_atom( - inst, gtk_selection_data_get_selection(seldata)); - - if (!state) - return; - - if (target == utf8_string_atom) - gtk_selection_data_set(seldata, target, 8, - (unsigned char *)state->pasteout_data_utf8, - state->pasteout_data_utf8_len); - else if (target == compound_text_atom) - gtk_selection_data_set(seldata, target, 8, - (unsigned char *)state->pasteout_data_ctext, - state->pasteout_data_ctext_len); - else - gtk_selection_data_set(seldata, target, 8, - (unsigned char *)state->pasteout_data, - state->pasteout_data_len); -} - -static gint selection_clear(GtkWidget *widget, GdkEventSelection *seldata, - gpointer data) -{ - GtkFrontend *inst = (GtkFrontend *)data; - struct clipboard_state *state = clipboard_from_atom( - inst, seldata->selection); - - if (!state) - return true; - - term_lost_clipboard_ownership(inst->term, state->clipboard); - if (state->pasteout_data) - sfree(state->pasteout_data); - if (state->pasteout_data_ctext) - sfree(state->pasteout_data_ctext); - if (state->pasteout_data_utf8) - sfree(state->pasteout_data_utf8); - state->pasteout_data = NULL; - state->pasteout_data_len = 0; - state->pasteout_data_ctext = NULL; - state->pasteout_data_ctext_len = 0; - state->pasteout_data_utf8 = NULL; - state->pasteout_data_utf8_len = 0; - return true; -} - -static void gtkwin_clip_request_paste(TermWin *tw, int clipboard) -{ - GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); - struct clipboard_state *state = &inst->clipstates[clipboard]; - - /* - * In Unix, pasting is asynchronous: all we can do at the - * moment is to call gtk_selection_convert(), and when the data - * comes back _then_ we can call term_do_paste(). - */ - - if (!inst->direct_to_font) { - /* - * First we attempt to retrieve the selection as a UTF-8 - * string (which we will convert to the correct code page - * before sending to the session, of course). If that - * fails, selection_received() will be informed and will - * fall back to an ordinary string. - */ - gtk_selection_convert(inst->area, state->atom, utf8_string_atom, - inst->input_event_time); - } else { - /* - * If we're in direct-to-font mode, we disable UTF-8 - * pasting, and go straight to ordinary string data. - */ - gtk_selection_convert(inst->area, state->atom, - GDK_SELECTION_TYPE_STRING, - inst->input_event_time); - } -} - -static void selection_received(GtkWidget *widget, GtkSelectionData *seldata, - guint time, gpointer data) -{ - GtkFrontend *inst = (GtkFrontend *)data; - char *text; - int length; -#ifndef NOT_X_WINDOWS - char **list; - bool free_list_required = false; - bool free_required = false; -#endif - int charset; - GdkAtom seldata_target = gtk_selection_data_get_target(seldata); - GdkAtom seldata_type = gtk_selection_data_get_data_type(seldata); - const guchar *seldata_data = gtk_selection_data_get_data(seldata); - gint seldata_length = gtk_selection_data_get_length(seldata); - wchar_t *paste; - int paste_len; - struct clipboard_state *state = clipboard_from_atom( - inst, gtk_selection_data_get_selection(seldata)); - - if (!state) - return; - - if (seldata_target == utf8_string_atom && seldata_length <= 0) { - /* - * Failed to get a UTF-8 selection string. Try compound - * text next. - */ - gtk_selection_convert(inst->area, state->atom, - compound_text_atom, - inst->input_event_time); - return; - } - - if (seldata_target == compound_text_atom && seldata_length <= 0) { - /* - * Failed to get UTF-8 or compound text. Try an ordinary - * string. - */ - gtk_selection_convert(inst->area, state->atom, - GDK_SELECTION_TYPE_STRING, - inst->input_event_time); - return; - } - - /* - * If we have data, but it's not of a type we can deal with, - * we have to ignore the data. - */ - if (seldata_length > 0 && - seldata_type != GDK_SELECTION_TYPE_STRING && - seldata_type != compound_text_atom && - seldata_type != utf8_string_atom) - return; - - /* - * If we have no data, try looking in a cut buffer. - */ - if (seldata_length <= 0) { -#ifndef NOT_X_WINDOWS - text = retrieve_cutbuffer(inst, &length); - if (length == 0) - return; - /* Xterm is rumoured to expect Latin-1, though I havn't checked the - * source, so use that as a de-facto standard. */ - charset = CS_ISO8859_1; - free_required = true; -#else - return; -#endif - } else { - /* - * Convert COMPOUND_TEXT into UTF-8. - */ - if (seldata_type == compound_text_atom) { -#ifndef NOT_X_WINDOWS - XTextProperty tp; - int ret, count; - - tp.value = (unsigned char *)seldata_data; - tp.encoding = (Atom) seldata_type; - tp.format = gtk_selection_data_get_format(seldata); - tp.nitems = seldata_length; - ret = inst->disp == NULL ? -1 : - Xutf8TextPropertyToTextList(inst->disp, &tp, &list, &count); - if (ret == 0 && count == 1) { - text = list[0]; - length = strlen(list[0]); - charset = CS_UTF8; - free_list_required = true; - } else -#endif - { - /* - * Compound text failed; fall back to STRING. - */ - gtk_selection_convert(inst->area, state->atom, - GDK_SELECTION_TYPE_STRING, - inst->input_event_time); - return; - } - } else { - text = (char *)seldata_data; - length = seldata_length; - charset = (seldata_type == utf8_string_atom ? - CS_UTF8 : inst->ucsdata.line_codepage); - } - } - - paste = snewn(length, wchar_t); - paste_len = mb_to_wc(charset, 0, text, length, paste, length); - - term_do_paste(inst->term, paste, paste_len); - - sfree(paste); - -#ifndef NOT_X_WINDOWS - if (free_list_required) - XFreeStringList(list); - if (free_required) - XFree(text); -#endif -} - -static void init_one_clipboard(GtkFrontend *inst, int clipboard) -{ - struct clipboard_state *state = &inst->clipstates[clipboard]; - - state->inst = inst; - state->clipboard = clipboard; -} - -void set_clipboard_atom(GtkFrontend *inst, int clipboard, GdkAtom atom) -{ - struct clipboard_state *state = &inst->clipstates[clipboard]; - - state->inst = inst; - state->clipboard = clipboard; - - state->atom = atom; -} - -void init_clipboard(GtkFrontend *inst) -{ -#ifndef NOT_X_WINDOWS - /* - * Ensure that all the cut buffers exist - according to the ICCCM, - * we must do this before we start using cut buffers. - */ - if (inst->disp) { - unsigned char empty[] = ""; - x11_ignore_error(inst->disp, BadMatch); - XChangeProperty(inst->disp, GDK_ROOT_WINDOW(), XA_CUT_BUFFER0, - XA_STRING, 8, PropModeAppend, empty, 0); - x11_ignore_error(inst->disp, BadMatch); - XChangeProperty(inst->disp, GDK_ROOT_WINDOW(), XA_CUT_BUFFER1, - XA_STRING, 8, PropModeAppend, empty, 0); - x11_ignore_error(inst->disp, BadMatch); - XChangeProperty(inst->disp, GDK_ROOT_WINDOW(), XA_CUT_BUFFER2, - XA_STRING, 8, PropModeAppend, empty, 0); - x11_ignore_error(inst->disp, BadMatch); - XChangeProperty(inst->disp, GDK_ROOT_WINDOW(), XA_CUT_BUFFER3, - XA_STRING, 8, PropModeAppend, empty, 0); - x11_ignore_error(inst->disp, BadMatch); - XChangeProperty(inst->disp, GDK_ROOT_WINDOW(), XA_CUT_BUFFER4, - XA_STRING, 8, PropModeAppend, empty, 0); - x11_ignore_error(inst->disp, BadMatch); - XChangeProperty(inst->disp, GDK_ROOT_WINDOW(), XA_CUT_BUFFER5, - XA_STRING, 8, PropModeAppend, empty, 0); - x11_ignore_error(inst->disp, BadMatch); - XChangeProperty(inst->disp, GDK_ROOT_WINDOW(), XA_CUT_BUFFER6, - XA_STRING, 8, PropModeAppend, empty, 0); - x11_ignore_error(inst->disp, BadMatch); - XChangeProperty(inst->disp, GDK_ROOT_WINDOW(), XA_CUT_BUFFER7, - XA_STRING, 8, PropModeAppend, empty, 0); - } -#endif - - inst->clipstates[CLIP_PRIMARY].atom = GDK_SELECTION_PRIMARY; - inst->clipstates[CLIP_CLIPBOARD].atom = clipboard_atom; - init_one_clipboard(inst, CLIP_PRIMARY); - init_one_clipboard(inst, CLIP_CLIPBOARD); - - g_signal_connect(G_OBJECT(inst->area), "selection_received", - G_CALLBACK(selection_received), inst); - g_signal_connect(G_OBJECT(inst->area), "selection_get", - G_CALLBACK(selection_get), inst); - g_signal_connect(G_OBJECT(inst->area), "selection_clear_event", - G_CALLBACK(selection_clear), inst); -} - -/* - * End of selection/clipboard handling. - * ---------------------------------------------------------------------- - */ - -#endif /* JUST_USE_GTK_CLIPBOARD_UTF8 */ - -static void set_window_titles(GtkFrontend *inst) -{ - /* - * We must always call set_icon_name after calling set_title, - * since set_title will write both names. Irritating, but such - * is life. - */ - gtk_window_set_title(GTK_WINDOW(inst->window), inst->wintitle); - if (!conf_get_bool(inst->conf, CONF_win_name_always)) - gdk_window_set_icon_name(gtk_widget_get_window(inst->window), - inst->icontitle); -} - -static void gtkwin_set_title(TermWin *tw, const char *title) -{ - GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); - sfree(inst->wintitle); - inst->wintitle = dupstr(title); - set_window_titles(inst); -} - -static void gtkwin_set_icon_title(TermWin *tw, const char *title) -{ - GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); - sfree(inst->icontitle); - inst->icontitle = dupstr(title); - set_window_titles(inst); -} - -static void gtkwin_set_scrollbar(TermWin *tw, int total, int start, int page) -{ - GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); - if (!conf_get_bool(inst->conf, CONF_scrollbar)) - return; - inst->ignore_sbar = true; - gtk_adjustment_set_lower(inst->sbar_adjust, 0); - gtk_adjustment_set_upper(inst->sbar_adjust, total); - gtk_adjustment_set_value(inst->sbar_adjust, start); - gtk_adjustment_set_page_size(inst->sbar_adjust, page); - gtk_adjustment_set_step_increment(inst->sbar_adjust, 1); - gtk_adjustment_set_page_increment(inst->sbar_adjust, page/2); -#if !GTK_CHECK_VERSION(3,18,0) - gtk_adjustment_changed(inst->sbar_adjust); -#endif - inst->ignore_sbar = false; -} - -void scrollbar_moved(GtkAdjustment *adj, GtkFrontend *inst) -{ - if (!conf_get_bool(inst->conf, CONF_scrollbar)) - return; - if (!inst->ignore_sbar) - term_scroll(inst->term, 1, (int)gtk_adjustment_get_value(adj)); -} - -static void show_scrollbar(GtkFrontend *inst, gboolean visible) -{ - inst->sbar_visible = visible; - if (visible) - gtk_widget_show(inst->sbar); - else - gtk_widget_hide(inst->sbar); -} - -static void gtkwin_set_cursor_pos(TermWin *tw, int x, int y) -{ - /* - * This is meaningless under X. - */ -} - -/* - * This is still called when mode==BELL_VISUAL, even though the - * visual bell is handled entirely within terminal.c, because we - * may want to perform additional actions on any kind of bell (for - * example, taskbar flashing in Windows). - */ -static void gtkwin_bell(TermWin *tw, int mode) -{ - /* GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); */ - if (mode == BELL_DEFAULT) - gdk_display_beep(gdk_display_get_default()); -} - -static int gtkwin_char_width(TermWin *tw, int uc) -{ - /* - * In this front end, double-width characters are handled using a - * separate font, so this can safely just return 1 always. - */ - return 1; -} - -static bool gtkwin_setup_draw_ctx(TermWin *tw) -{ - GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); - - if (!gtk_widget_get_window(inst->area)) - return false; - - inst->uctx.type = inst->drawtype; -#ifdef DRAW_TEXT_GDK - if (inst->uctx.type == DRAWTYPE_GDK) { - /* If we're doing GDK-based drawing, then we also expect - * inst->pixmap to exist. */ - inst->uctx.u.gdk.target = inst->pixmap; - inst->uctx.u.gdk.gc = gdk_gc_new(gtk_widget_get_window(inst->area)); - } -#endif -#ifdef DRAW_TEXT_CAIRO - if (inst->uctx.type == DRAWTYPE_CAIRO) { - inst->uctx.u.cairo.widget = GTK_WIDGET(inst->area); - /* If we're doing Cairo drawing, we expect inst->surface to - * exist, and we draw to that first, regardless of whether we - * subsequently copy the results to inst->pixmap. */ - inst->uctx.u.cairo.cr = cairo_create(inst->surface); - cairo_scale(inst->uctx.u.cairo.cr, inst->scale, inst->scale); - cairo_setup_draw_ctx(inst); - } -#endif - return true; -} - -static void gtkwin_free_draw_ctx(TermWin *tw) -{ - GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); -#ifdef DRAW_TEXT_GDK - if (inst->uctx.type == DRAWTYPE_GDK) { - gdk_gc_unref(inst->uctx.u.gdk.gc); - } -#endif -#ifdef DRAW_TEXT_CAIRO - if (inst->uctx.type == DRAWTYPE_CAIRO) { - cairo_destroy(inst->uctx.u.cairo.cr); - } -#endif -} - - -static void draw_update(GtkFrontend *inst, int x, int y, int w, int h) -{ -#if defined DRAW_TEXT_CAIRO && !defined NO_BACKING_PIXMAPS - if (inst->uctx.type == DRAWTYPE_CAIRO) { - /* - * If inst->surface and inst->pixmap both exist, then we've - * just drawn new content to the former which we must copy to - * the latter. - */ - cairo_t *cr = gdk_cairo_create(inst->pixmap); - cairo_set_source_surface(cr, inst->surface, 0, 0); - cairo_rectangle(cr, x, y, w, h); - cairo_fill(cr); - cairo_destroy(cr); - } -#endif - - /* - * Now we just queue a window redraw, which will cause - * inst->surface or inst->pixmap (whichever is appropriate for our - * compile mode) to be copied to the real window when we receive - * the resulting "expose" or "draw" event. - * - * Amazingly, this one API call is actually valid in all versions - * of GTK :-) - */ - gtk_widget_queue_draw_area(inst->area, x, y, w, h); -} - -#ifdef DRAW_TEXT_CAIRO -static void cairo_set_source_rgb_dim(cairo_t *cr, double r, double g, double b, - bool dim) -{ - if (dim) - cairo_set_source_rgb(cr, r * 2 / 3, g * 2 / 3, b * 2 / 3); - else - cairo_set_source_rgb(cr, r, g, b); -} -#endif - -static void draw_set_colour(GtkFrontend *inst, int col, bool dim) -{ -#ifdef DRAW_TEXT_GDK - if (inst->uctx.type == DRAWTYPE_GDK) { - if (dim) { -#if GTK_CHECK_VERSION(2,0,0) - GdkColor color; - color.red = inst->cols[col].red * 2 / 3; - color.green = inst->cols[col].green * 2 / 3; - color.blue = inst->cols[col].blue * 2 / 3; - gdk_gc_set_rgb_fg_color(inst->uctx.u.gdk.gc, &color); -#else - /* Poor GTK1 fallback */ - gdk_gc_set_foreground(inst->uctx.u.gdk.gc, &inst->cols[col]); -#endif - } else { - gdk_gc_set_foreground(inst->uctx.u.gdk.gc, &inst->cols[col]); - } - } -#endif -#ifdef DRAW_TEXT_CAIRO - if (inst->uctx.type == DRAWTYPE_CAIRO) { - cairo_set_source_rgb_dim(inst->uctx.u.cairo.cr, - inst->cols[col].red / 65535.0, - inst->cols[col].green / 65535.0, - inst->cols[col].blue / 65535.0, dim); - } -#endif -} - -static void draw_set_colour_rgb(GtkFrontend *inst, optionalrgb orgb, bool dim) -{ -#ifdef DRAW_TEXT_GDK - if (inst->uctx.type == DRAWTYPE_GDK) { -#if GTK_CHECK_VERSION(2,0,0) - GdkColor color; - color.red = orgb.r * 256; - color.green = orgb.g * 256; - color.blue = orgb.b * 256; - if (dim) { - color.red = color.red * 2 / 3; - color.green = color.green * 2 / 3; - color.blue = color.blue * 2 / 3; - } - gdk_gc_set_rgb_fg_color(inst->uctx.u.gdk.gc, &color); -#else - /* Poor GTK1 fallback */ - gdk_gc_set_foreground(inst->uctx.u.gdk.gc, &inst->cols[256]); -#endif - } -#endif -#ifdef DRAW_TEXT_CAIRO - if (inst->uctx.type == DRAWTYPE_CAIRO) { - cairo_set_source_rgb_dim(inst->uctx.u.cairo.cr, orgb.r / 255.0, - orgb.g / 255.0, orgb.b / 255.0, dim); - } -#endif -} - -static void draw_rectangle(GtkFrontend *inst, bool filled, - int x, int y, int w, int h) -{ -#ifdef DRAW_TEXT_GDK - if (inst->uctx.type == DRAWTYPE_GDK) { - gdk_draw_rectangle(inst->uctx.u.gdk.target, inst->uctx.u.gdk.gc, - filled, x, y, w, h); - } -#endif -#ifdef DRAW_TEXT_CAIRO - if (inst->uctx.type == DRAWTYPE_CAIRO) { - cairo_new_path(inst->uctx.u.cairo.cr); - if (filled) { - cairo_rectangle(inst->uctx.u.cairo.cr, x, y, w, h); - cairo_fill(inst->uctx.u.cairo.cr); - } else { - cairo_rectangle(inst->uctx.u.cairo.cr, - x + 0.5, y + 0.5, w, h); - cairo_close_path(inst->uctx.u.cairo.cr); - cairo_stroke(inst->uctx.u.cairo.cr); - } - } -#endif -} - -static void draw_clip(GtkFrontend *inst, int x, int y, int w, int h) -{ -#ifdef DRAW_TEXT_GDK - if (inst->uctx.type == DRAWTYPE_GDK) { - GdkRectangle r; - - r.x = x; - r.y = y; - r.width = w; - r.height = h; - - gdk_gc_set_clip_rectangle(inst->uctx.u.gdk.gc, &r); - } -#endif -#ifdef DRAW_TEXT_CAIRO - if (inst->uctx.type == DRAWTYPE_CAIRO) { - cairo_reset_clip(inst->uctx.u.cairo.cr); - cairo_new_path(inst->uctx.u.cairo.cr); - cairo_rectangle(inst->uctx.u.cairo.cr, x, y, w, h); - cairo_clip(inst->uctx.u.cairo.cr); - } -#endif -} - -static void draw_point(GtkFrontend *inst, int x, int y) -{ -#ifdef DRAW_TEXT_GDK - if (inst->uctx.type == DRAWTYPE_GDK) { - gdk_draw_point(inst->uctx.u.gdk.target, inst->uctx.u.gdk.gc, x, y); - } -#endif -#ifdef DRAW_TEXT_CAIRO - if (inst->uctx.type == DRAWTYPE_CAIRO) { - cairo_new_path(inst->uctx.u.cairo.cr); - cairo_rectangle(inst->uctx.u.cairo.cr, x, y, 1, 1); - cairo_fill(inst->uctx.u.cairo.cr); - } -#endif -} - -static void draw_line(GtkFrontend *inst, int x0, int y0, int x1, int y1) -{ -#ifdef DRAW_TEXT_GDK - if (inst->uctx.type == DRAWTYPE_GDK) { - gdk_draw_line(inst->uctx.u.gdk.target, inst->uctx.u.gdk.gc, - x0, y0, x1, y1); - } -#endif -#ifdef DRAW_TEXT_CAIRO - if (inst->uctx.type == DRAWTYPE_CAIRO) { - cairo_new_path(inst->uctx.u.cairo.cr); - cairo_move_to(inst->uctx.u.cairo.cr, x0 + 0.5, y0 + 0.5); - cairo_line_to(inst->uctx.u.cairo.cr, x1 + 0.5, y1 + 0.5); - cairo_stroke(inst->uctx.u.cairo.cr); - } -#endif -} - -static void draw_stretch_before(GtkFrontend *inst, int x, int y, - int w, bool wdouble, - int h, bool hdouble, bool hbothalf) -{ -#ifdef DRAW_TEXT_CAIRO - if (inst->uctx.type == DRAWTYPE_CAIRO) { - cairo_matrix_t matrix; - - matrix.xy = 0; - matrix.yx = 0; - - if (wdouble) { - matrix.xx = 2; - matrix.x0 = -x; - } else { - matrix.xx = 1; - matrix.x0 = 0; - } - - if (hdouble) { - matrix.yy = 2; - if (hbothalf) { - matrix.y0 = -(y+h); - } else { - matrix.y0 = -y; - } - } else { - matrix.yy = 1; - matrix.y0 = 0; - } - cairo_transform(inst->uctx.u.cairo.cr, &matrix); - } -#endif -} - -static void draw_stretch_after(GtkFrontend *inst, int x, int y, - int w, bool wdouble, - int h, bool hdouble, bool hbothalf) -{ -#ifdef DRAW_TEXT_GDK -#ifndef NO_BACKING_PIXMAPS - if (inst->uctx.type == DRAWTYPE_GDK) { - /* - * I can't find any plausible StretchBlt equivalent in the X - * server, so I'm going to do this the slow and painful way. - * This will involve repeated calls to gdk_draw_pixmap() to - * stretch the text horizontally. It's O(N^2) in time and O(N) - * in network bandwidth, but you try thinking of a better way. - * :-( - */ - int i; - if (wdouble) { - for (i = 0; i < w; i++) { - gdk_draw_pixmap(inst->uctx.u.gdk.target, - inst->uctx.u.gdk.gc, - inst->uctx.u.gdk.target, - x + 2*i, y, - x + 2*i+1, y, - w - i, h); - } - w *= 2; - } - - if (hdouble) { - int dt, db; - /* Now stretch vertically, in the same way. */ - if (hbothalf) - dt = 0, db = 1; - else - dt = 1, db = 0; - for (i = 0; i < h; i += 2) { - gdk_draw_pixmap(inst->uctx.u.gdk.target, - inst->uctx.u.gdk.gc, - inst->uctx.u.gdk.target, - x, y + dt*i + db, - x, y + dt*(i+1), - w, h-i-1); - } - } - } -#else -#error No way to implement stretching in GDK without a reliable backing pixmap -#endif -#endif /* DRAW_TEXT_GDK */ -#ifdef DRAW_TEXT_CAIRO - if (inst->uctx.type == DRAWTYPE_CAIRO) { - cairo_set_matrix(inst->uctx.u.cairo.cr, - &inst->uctx.u.cairo.origmatrix); - } -#endif -} - -static void draw_backing_rect(GtkFrontend *inst) -{ - int w, h; - - if (!win_setup_draw_ctx(&inst->termwin)) - return; - - w = inst->width * inst->font_width + 2*inst->window_border; - h = inst->height * inst->font_height + 2*inst->window_border; - draw_set_colour(inst, 258, false); - draw_rectangle(inst, true, 0, 0, w, h); - draw_update(inst, 0, 0, w, h); - win_free_draw_ctx(&inst->termwin); -} - -/* - * Draw a line of text in the window, at given character - * coordinates, in given attributes. - * - * We are allowed to fiddle with the contents of `text'. - */ -static void do_text_internal( - GtkFrontend *inst, int x, int y, wchar_t *text, int len, - unsigned long attr, int lattr, truecolour truecolour) -{ - int ncombining; - int nfg, nbg, t, fontid, rlen, widefactor; - bool bold; - bool monochrome = - gdk_visual_get_depth(gtk_widget_get_visual(inst->area)) == 1; - - if (attr & TATTR_COMBINING) { - ncombining = len; - len = 1; - } else - ncombining = 1; - - if (monochrome) - truecolour.fg = truecolour.bg = optionalrgb_none; - - nfg = ((monochrome ? ATTR_DEFFG : (attr & ATTR_FGMASK)) >> ATTR_FGSHIFT); - nbg = ((monochrome ? ATTR_DEFBG : (attr & ATTR_BGMASK)) >> ATTR_BGSHIFT); - if (!!(attr & ATTR_REVERSE) ^ (monochrome && (attr & TATTR_ACTCURS))) { - struct optionalrgb trgb; - - t = nfg; - nfg = nbg; - nbg = t; - - trgb = truecolour.fg; - truecolour.fg = truecolour.bg; - truecolour.bg = trgb; - } - if ((inst->bold_style & 2) && (attr & ATTR_BOLD)) { - if (nfg < 16) nfg |= 8; - else if (nfg >= 256) nfg |= 1; - } - if ((inst->bold_style & 2) && (attr & ATTR_BLINK)) { - if (nbg < 16) nbg |= 8; - else if (nbg >= 256) nbg |= 1; - } - if ((attr & TATTR_ACTCURS) && !monochrome) { - truecolour.fg = truecolour.bg = optionalrgb_none; - nfg = 260; - nbg = 261; - attr &= ~ATTR_DIM; /* don't dim the cursor */ - } - - fontid = 0; - - if (attr & ATTR_WIDE) { - widefactor = 2; - fontid |= 2; - } else { - widefactor = 1; - } - - if ((attr & ATTR_BOLD) && (inst->bold_style & 1)) { - bold = true; - fontid |= 1; - } else { - bold = false; - } - - if (!inst->fonts[fontid]) { - int i; - /* - * Fall back through font ids with subsets of this one's - * set bits, in order. - */ - for (i = fontid; i-- > 0 ;) { - if (i & ~fontid) - continue; /* some other bit is set */ - if (inst->fonts[i]) { - fontid = i; - break; - } - } - assert(inst->fonts[fontid]); /* we should at least have hit zero */ - } - - if ((lattr & LATTR_MODE) != LATTR_NORM) { - x *= 2; - if (x >= inst->term->cols) - return; - if (x + len*2*widefactor > inst->term->cols) { - len = (inst->term->cols-x)/2/widefactor;/* trim to LH half */ - if (len == 0) - return; /* rounded down half a double-width char to zero */ - } - rlen = len * 2; - } else - rlen = len; - - draw_clip(inst, - x*inst->font_width+inst->window_border, - y*inst->font_height+inst->window_border, - rlen*widefactor*inst->font_width, - inst->font_height); - - if ((lattr & LATTR_MODE) != LATTR_NORM) { - draw_stretch_before(inst, - x*inst->font_width+inst->window_border, - y*inst->font_height+inst->window_border, - rlen*widefactor*inst->font_width, true, - inst->font_height, - ((lattr & LATTR_MODE) != LATTR_WIDE), - ((lattr & LATTR_MODE) == LATTR_BOT)); - } - - if (truecolour.bg.enabled) - draw_set_colour_rgb(inst, truecolour.bg, attr & ATTR_DIM); - else - draw_set_colour(inst, nbg, attr & ATTR_DIM); - draw_rectangle(inst, true, - x*inst->font_width+inst->window_border, - y*inst->font_height+inst->window_border, - rlen*widefactor*inst->font_width, inst->font_height); - - if (truecolour.fg.enabled) - draw_set_colour_rgb(inst, truecolour.fg, attr & ATTR_DIM); - else - draw_set_colour(inst, nfg, attr & ATTR_DIM); - if (ncombining > 1) { - assert(len == 1); - unifont_draw_combining(&inst->uctx, inst->fonts[fontid], - x*inst->font_width+inst->window_border, - (y*inst->font_height+inst->window_border+ - inst->fonts[0]->ascent), - text, ncombining, widefactor > 1, - bold, inst->font_width); - } else { - unifont_draw_text(&inst->uctx, inst->fonts[fontid], - x*inst->font_width+inst->window_border, - (y*inst->font_height+inst->window_border+ - inst->fonts[0]->ascent), - text, len, widefactor > 1, - bold, inst->font_width); - } - - if (attr & ATTR_UNDER) { - int uheight = inst->fonts[0]->ascent + 1; - if (uheight >= inst->font_height) - uheight = inst->font_height - 1; - draw_line(inst, x*inst->font_width+inst->window_border, - y*inst->font_height + uheight + inst->window_border, - (x+len)*widefactor*inst->font_width-1+inst->window_border, - y*inst->font_height + uheight + inst->window_border); - } - - if (attr & ATTR_STRIKE) { - int sheight = inst->fonts[fontid]->strikethrough_y; - draw_line(inst, x*inst->font_width+inst->window_border, - y*inst->font_height + sheight + inst->window_border, - (x+len)*widefactor*inst->font_width-1+inst->window_border, - y*inst->font_height + sheight + inst->window_border); - } - - if ((lattr & LATTR_MODE) != LATTR_NORM) { - draw_stretch_after(inst, - x*inst->font_width+inst->window_border, - y*inst->font_height+inst->window_border, - rlen*widefactor*inst->font_width, true, - inst->font_height, - ((lattr & LATTR_MODE) != LATTR_WIDE), - ((lattr & LATTR_MODE) == LATTR_BOT)); - } -} - -static void gtkwin_draw_text( - TermWin *tw, int x, int y, wchar_t *text, int len, - unsigned long attr, int lattr, truecolour truecolour) -{ - GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); - int widefactor; - - do_text_internal(inst, x, y, text, len, attr, lattr, truecolour); - - if (attr & ATTR_WIDE) { - widefactor = 2; - } else { - widefactor = 1; - } - - if ((lattr & LATTR_MODE) != LATTR_NORM) { - x *= 2; - if (x >= inst->term->cols) - return; - if (x + len*2*widefactor > inst->term->cols) - len = (inst->term->cols-x)/2/widefactor;/* trim to LH half */ - len *= 2; - } - - draw_update(inst, - x*inst->font_width+inst->window_border, - y*inst->font_height+inst->window_border, - len*widefactor*inst->font_width, inst->font_height); -} - -static void gtkwin_draw_cursor( - TermWin *tw, int x, int y, wchar_t *text, int len, - unsigned long attr, int lattr, truecolour truecolour) -{ - GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); - bool active, passive; - int widefactor; - - if (attr & TATTR_PASCURS) { - attr &= ~TATTR_PASCURS; - passive = true; - } else - passive = false; - if ((attr & TATTR_ACTCURS) && inst->cursor_type != 0) { - attr &= ~TATTR_ACTCURS; - active = true; - } else - active = false; - do_text_internal(inst, x, y, text, len, attr, lattr, truecolour); - - if (attr & TATTR_COMBINING) - len = 1; - - if (attr & ATTR_WIDE) { - widefactor = 2; - } else { - widefactor = 1; - } - - if ((lattr & LATTR_MODE) != LATTR_NORM) { - x *= 2; - if (x >= inst->term->cols) - return; - if (x + len*2*widefactor > inst->term->cols) - len = (inst->term->cols-x)/2/widefactor;/* trim to LH half */ - len *= 2; - } - - if (inst->cursor_type == 0) { - /* - * An active block cursor will already have been done by - * the above do_text call, so we only need to do anything - * if it's passive. - */ - if (passive) { - draw_set_colour(inst, 261, false); - draw_rectangle(inst, false, - x*inst->font_width+inst->window_border, - y*inst->font_height+inst->window_border, - len*widefactor*inst->font_width-1, - inst->font_height-1); - } - } else { - int uheight; - int startx, starty, dx, dy, length, i; - - int char_width; - - if ((attr & ATTR_WIDE) || (lattr & LATTR_MODE) != LATTR_NORM) - char_width = 2*inst->font_width; - else - char_width = inst->font_width; - - if (inst->cursor_type == 1) { - uheight = inst->fonts[0]->ascent + 1; - if (uheight >= inst->font_height) - uheight = inst->font_height - 1; - - startx = x * inst->font_width + inst->window_border; - starty = y * inst->font_height + inst->window_border + uheight; - dx = 1; - dy = 0; - length = len * widefactor * char_width; - } else { - int xadjust = 0; - if (attr & TATTR_RIGHTCURS) - xadjust = char_width - 1; - startx = x * inst->font_width + inst->window_border + xadjust; - starty = y * inst->font_height + inst->window_border; - dx = 0; - dy = 1; - length = inst->font_height; - } - - draw_set_colour(inst, 261, false); - if (passive) { - for (i = 0; i < length; i++) { - if (i % 2 == 0) { - draw_point(inst, startx, starty); - } - startx += dx; - starty += dy; - } - } else if (active) { - draw_line(inst, startx, starty, - startx + (length-1) * dx, starty + (length-1) * dy); - } /* else no cursor (e.g., blinked off) */ - } - - draw_update(inst, - x*inst->font_width+inst->window_border, - y*inst->font_height+inst->window_border, - len*widefactor*inst->font_width, inst->font_height); - -#if GTK_CHECK_VERSION(2,0,0) - { - GdkRectangle cursorrect; - cursorrect.x = x*inst->font_width+inst->window_border; - cursorrect.y = y*inst->font_height+inst->window_border; - cursorrect.width = len*widefactor*inst->font_width; - cursorrect.height = inst->font_height; - gtk_im_context_set_cursor_location(inst->imc, &cursorrect); - } -#endif -} - -#if !GTK_CHECK_VERSION(2,0,0) -/* - * For GTK 1, manual code to scale an in-memory XPM, producing a new - * one as output. It will be ugly, but good enough to use as a trust - * sigil. - */ -struct XpmHolder { - char **strings; - size_t nstrings; -}; - -static void xpmholder_free(XpmHolder *xh) -{ - for (size_t i = 0; i < xh->nstrings; i++) - sfree(xh->strings[i]); - sfree(xh->strings); - sfree(xh); -} - -static XpmHolder *xpm_scale(const char *const *xpm, int wo, int ho) -{ - /* Get image dimensions, # colours, and chars-per-pixel */ - int wi = 0, hi = 0, nc = 0, cpp = 0; - int retd = sscanf(xpm[0], "%d %d %d %d", &wi, &hi, &nc, &cpp); - assert(retd == 4); - - /* Make output XpmHolder */ - XpmHolder *xh = snew(XpmHolder); - xh->nstrings = 1 + nc + ho; - xh->strings = snewn(xh->nstrings, char *); - - /* Set up header */ - xh->strings[0] = dupprintf("%d %d %d %d", wo, ho, nc, cpp); - for (int i = 0; i < nc; i++) - xh->strings[1 + i] = dupstr(xpm[1 + i]); - - /* Scale image */ - for (int yo = 0; yo < ho; yo++) { - int yi = yo * hi / ho; - char *ro = snewn(cpp * wo + 1, char); - ro[cpp * wo] = '\0'; - xh->strings[1 + nc + yo] = ro; - const char *ri = xpm[1 + nc + yi]; - - for (int xo = 0; xo < wo; xo++) { - int xi = xo * wi / wo; - memcpy(ro + cpp * xo, ri + cpp * xi, cpp); - } - } - - return xh; -} -#endif /* !GTK_CHECK_VERSION(2,0,0) */ - -static void gtkwin_draw_trust_sigil(TermWin *tw, int cx, int cy) -{ - GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); - - int x = cx * inst->font_width + inst->window_border; - int y = cy * inst->font_height + inst->window_border; - int w = 2*inst->font_width, h = inst->font_height; - - if (inst->trust_sigil_w != w || inst->trust_sigil_h != h || -#if GTK_CHECK_VERSION(2,0,0) - !inst->trust_sigil_pb -#else - !inst->trust_sigil_pm -#endif - ) { - -#if GTK_CHECK_VERSION(2,0,0) - if (inst->trust_sigil_pb) - g_object_unref(G_OBJECT(inst->trust_sigil_pb)); -#else - if (inst->trust_sigil_pm) - gdk_pixmap_unref(inst->trust_sigil_pm); -#endif - - int best_icon_index = 0; - unsigned score = UINT_MAX; - for (int i = 0; i < n_main_icon; i++) { - int iw, ih; - if (sscanf(main_icon[i][0], "%d %d", &iw, &ih) == 2) { - int this_excess = (iw + ih) - (w + h); - unsigned this_score = (abs(this_excess) | - (this_excess > 0 ? 0 : 0x80000000U)); - if (this_score < score) { - best_icon_index = i; - score = this_score; - } - } - } - -#if GTK_CHECK_VERSION(2,0,0) - GdkPixbuf *icon_unscaled = gdk_pixbuf_new_from_xpm_data( - (const gchar **)main_icon[best_icon_index]); - inst->trust_sigil_pb = gdk_pixbuf_scale_simple( - icon_unscaled, w, h, GDK_INTERP_BILINEAR); - g_object_unref(G_OBJECT(icon_unscaled)); -#else - XpmHolder *xh = xpm_scale(main_icon[best_icon_index], w, h); - inst->trust_sigil_pm = gdk_pixmap_create_from_xpm_d( - gtk_widget_get_window(inst->window), NULL, - &inst->cols[258], xh->strings); - xpmholder_free(xh); -#endif - - inst->trust_sigil_w = w; - inst->trust_sigil_h = h; - } - -#ifdef DRAW_TEXT_GDK - if (inst->uctx.type == DRAWTYPE_GDK) { -#if GTK_CHECK_VERSION(2,0,0) - gdk_draw_pixbuf(inst->uctx.u.gdk.target, inst->uctx.u.gdk.gc, - inst->trust_sigil_pb, 0, 0, x, y, w, h, - GDK_RGB_DITHER_NORMAL, 0, 0); -#else - gdk_draw_pixmap(inst->uctx.u.gdk.target, inst->uctx.u.gdk.gc, - inst->trust_sigil_pm, 0, 0, x, y, w, h); -#endif - } -#endif -#ifdef DRAW_TEXT_CAIRO - if (inst->uctx.type == DRAWTYPE_CAIRO) { - inst->uctx.u.cairo.widget = GTK_WIDGET(inst->area); - cairo_save(inst->uctx.u.cairo.cr); - cairo_translate(inst->uctx.u.cairo.cr, x, y); - gdk_cairo_set_source_pixbuf(inst->uctx.u.cairo.cr, - inst->trust_sigil_pb, 0, 0); - cairo_rectangle(inst->uctx.u.cairo.cr, 0, 0, w, h); - cairo_fill(inst->uctx.u.cairo.cr); - cairo_restore(inst->uctx.u.cairo.cr); - } -#endif - - draw_update(inst, x, y, w, h); -} - -GdkCursor *make_mouse_ptr(GtkFrontend *inst, int cursor_val) -{ - if (cursor_val == -1) { -#if GTK_CHECK_VERSION(2,16,0) - cursor_val = GDK_BLANK_CURSOR; -#else - /* - * Work around absence of GDK_BLANK_CURSOR by inventing a - * blank pixmap. - */ - GdkCursor *ret; - GdkColor bg = { 0, 0, 0, 0 }; - GdkPixmap *pm = gdk_pixmap_new(NULL, 1, 1, 1); - GdkGC *gc = gdk_gc_new(pm); - gdk_gc_set_foreground(gc, &bg); - gdk_draw_rectangle(pm, gc, 1, 0, 0, 1, 1); - gdk_gc_unref(gc); - ret = gdk_cursor_new_from_pixmap(pm, pm, &bg, &bg, 1, 1); - gdk_pixmap_unref(pm); - return ret; -#endif - } - - return gdk_cursor_new(cursor_val); -} - -void modalfatalbox(const char *p, ...) -{ - va_list ap; - fprintf(stderr, "FATAL ERROR: "); - va_start(ap, p); - vfprintf(stderr, p, ap); - va_end(ap); - fputc('\n', stderr); - exit(1); -} - -static const char *gtk_seat_get_x_display(Seat *seat) -{ - return gdk_get_display(); -} - -#ifndef NOT_X_WINDOWS -static bool gtk_seat_get_windowid(Seat *seat, long *id) -{ - GtkFrontend *inst = container_of(seat, GtkFrontend, seat); - GdkWindow *window = gtk_widget_get_window(inst->area); - if (!GDK_IS_X11_WINDOW(window)) - return false; - *id = GDK_WINDOW_XID(window); - return true; -} -#endif - -char *setup_fonts_ucs(GtkFrontend *inst) -{ - bool shadowbold = conf_get_bool(inst->conf, CONF_shadowbold); - int shadowboldoffset = conf_get_int(inst->conf, CONF_shadowboldoffset); - FontSpec *fs; - unifont *fonts[4]; - int i; - - fs = conf_get_fontspec(inst->conf, CONF_font); - fonts[0] = multifont_create(inst->area, fs->name, false, false, - shadowboldoffset, shadowbold); - if (!fonts[0]) { - return dupprintf("unable to load font \"%s\"", fs->name); - } - - fs = conf_get_fontspec(inst->conf, CONF_boldfont); - if (shadowbold || !fs->name[0]) { - fonts[1] = NULL; - } else { - fonts[1] = multifont_create(inst->area, fs->name, false, true, - shadowboldoffset, shadowbold); - if (!fonts[1]) { - if (fonts[0]) - unifont_destroy(fonts[0]); - return dupprintf("unable to load bold font \"%s\"", fs->name); - } - } - - fs = conf_get_fontspec(inst->conf, CONF_widefont); - if (fs->name[0]) { - fonts[2] = multifont_create(inst->area, fs->name, true, false, - shadowboldoffset, shadowbold); - if (!fonts[2]) { - for (i = 0; i < 2; i++) - if (fonts[i]) - unifont_destroy(fonts[i]); - return dupprintf("unable to load wide font \"%s\"", fs->name); - } - } else { - fonts[2] = NULL; - } - - fs = conf_get_fontspec(inst->conf, CONF_wideboldfont); - if (shadowbold || !fs->name[0]) { - fonts[3] = NULL; - } else { - fonts[3] = multifont_create(inst->area, fs->name, true, true, - shadowboldoffset, shadowbold); - if (!fonts[3]) { - for (i = 0; i < 3; i++) - if (fonts[i]) - unifont_destroy(fonts[i]); - return dupprintf("unable to load wide bold font \"%s\"", fs->name); - } - } - - /* - * Now we've got past all the possible error conditions, we can - * actually update our state. - */ - - for (i = 0; i < 4; i++) { - if (inst->fonts[i]) - unifont_destroy(inst->fonts[i]); - inst->fonts[i] = fonts[i]; - } - - if (inst->font_width != inst->fonts[0]->width || - inst->font_height != inst->fonts[0]->height) { - - inst->font_width = inst->fonts[0]->width; - inst->font_height = inst->fonts[0]->height; - - /* - * The font size has changed, so force the next call to - * drawing_area_setup to regenerate the backing surface. - */ - inst->drawing_area_setup_needed = true; - } - - inst->direct_to_font = init_ucs(&inst->ucsdata, - conf_get_str(inst->conf, CONF_line_codepage), - conf_get_bool(inst->conf, CONF_utf8_override), - inst->fonts[0]->public_charset, - conf_get_int(inst->conf, CONF_vtmode)); - - inst->drawtype = inst->fonts[0]->preferred_drawtype; - - return NULL; -} - -#if GTK_CHECK_VERSION(3,0,0) -struct find_app_menu_bar_ctx { - GtkWidget *area, *menubar; -}; -static void find_app_menu_bar(GtkWidget *widget, gpointer data) -{ - struct find_app_menu_bar_ctx *ctx = (struct find_app_menu_bar_ctx *)data; - if (widget != ctx->area && GTK_IS_MENU_BAR(widget)) - ctx->menubar = widget; -} -#endif - -static void compute_geom_hints(GtkFrontend *inst, GdkGeometry *geom) -{ - /* - * Unused fields in geom. - */ - geom->max_width = geom->max_height = -1; - geom->min_aspect = geom->max_aspect = 0; - - /* - * Set up the geometry fields we care about, with reference to - * just the drawing area. We'll correct for other widgets in a - * moment. - */ - geom->min_width = inst->font_width + 2*inst->window_border; - geom->min_height = inst->font_height + 2*inst->window_border; - geom->base_width = 2*inst->window_border; - geom->base_height = 2*inst->window_border; - geom->width_inc = inst->font_width; - geom->height_inc = inst->font_height; - - /* - * If we've got a scrollbar visible, then we must include its - * width as part of the base and min width, and also ensure that - * our window's minimum height is at least the height required by - * the scrollbar. - * - * In the latter case, we must also take care to arrange that - * (geom->min_height - geom->base_height) is an integer multiple of - * geom->height_inc, because if it's not, then some window managers - * (we know of xfwm4) get confused, with the effect that they - * resize our window to a height based on min_height instead of - * base_height, which we then round down and the window ends up - * too short. - */ - if (inst->sbar_visible) { - GtkRequisition req; - int min_sb_height; - -#if GTK_CHECK_VERSION(3,0,0) - gtk_widget_get_preferred_size(inst->sbar, &req, NULL); -#else - gtk_widget_size_request(inst->sbar, &req); -#endif - - /* Compute rounded-up scrollbar height. */ - min_sb_height = req.height; - min_sb_height += geom->height_inc - 1; - min_sb_height -= ((min_sb_height - geom->base_height%geom->height_inc) - % geom->height_inc); - - geom->min_width += req.width; - geom->base_width += req.width; - if (geom->min_height < min_sb_height) - geom->min_height = min_sb_height; - } - -#if GTK_CHECK_VERSION(3,0,0) - /* - * And if we're running a gtkapp.c based program and - * GtkApplicationWindow has given us a menu bar inside the window, - * then we must take that into account as well. - * - * In its unbounded wisdom, GtkApplicationWindow doesn't actually - * give us a direct function call to _find_ the menu bar widget. - * Fortunately, we can find it by enumerating the children of the - * top-level window and looking for one we didn't put there - * ourselves. - */ - { - struct find_app_menu_bar_ctx ctx[1]; - ctx->area = inst->area; - ctx->menubar = NULL; - gtk_container_foreach(GTK_CONTAINER(inst->window), - find_app_menu_bar, ctx); - - if (ctx->menubar) { - GtkRequisition req; - int min_menu_width; - gtk_widget_get_preferred_size(ctx->menubar, NULL, &req); - - /* - * This time, the height adjustment is easy (the menu bar - * sits above everything), but we have to take care with - * the _width_ to ensure we keep min_width and base_width - * congruent modulo width_inc. - */ - geom->min_height += req.height; - geom->base_height += req.height; - - min_menu_width = req.width; - min_menu_width += geom->width_inc - 1; - min_menu_width -= - ((min_menu_width - geom->base_width%geom->width_inc) - % geom->width_inc); - if (geom->min_width < min_menu_width) - geom->min_width = min_menu_width; - } - } -#endif -} - -void set_geom_hints(GtkFrontend *inst) -{ - const struct BackendVtable *vt; - GdkGeometry geom; - gint flags = GDK_HINT_MIN_SIZE | GDK_HINT_BASE_SIZE | GDK_HINT_RESIZE_INC; - compute_geom_hints(inst, &geom); -#if GTK_CHECK_VERSION(2,0,0) - if (inst->gotpos) - flags |= GDK_HINT_USER_POS; -#endif - vt = backend_vt_from_proto(conf_get_int(inst->conf, CONF_protocol)); - if (vt && vt->flags & BACKEND_RESIZE_FORBIDDEN) { - /* Window resizing forbidden. Set both minimum and maximum - * dimensions to be the initial size. */ - geom.min_width = inst->width*inst->font_width + 2*inst->window_border; - geom.min_height = inst->height*inst->font_height + 2*inst->window_border; - geom.max_width = geom.min_width; - geom.max_height = geom.min_height; - flags |= GDK_HINT_MAX_SIZE; - } - gtk_window_set_geometry_hints(GTK_WINDOW(inst->window), - NULL, &geom, flags); -} - -#if GTK_CHECK_VERSION(2,0,0) -static void compute_whole_window_size(GtkFrontend *inst, - int wchars, int hchars, - int *wpix, int *hpix) -{ - GdkGeometry geom; - compute_geom_hints(inst, &geom); - if (wpix) *wpix = geom.base_width + wchars * geom.width_inc; - if (hpix) *hpix = geom.base_height + hchars * geom.height_inc; -} -#endif - -void clear_scrollback_menuitem(GtkMenuItem *item, gpointer data) -{ - GtkFrontend *inst = (GtkFrontend *)data; - term_clrsb(inst->term); -} - -void reset_terminal_menuitem(GtkMenuItem *item, gpointer data) -{ - GtkFrontend *inst = (GtkFrontend *)data; - term_pwron(inst->term, true); - if (inst->ldisc) - ldisc_echoedit_update(inst->ldisc); -} - -void copy_clipboard_menuitem(GtkMenuItem *item, gpointer data) -{ - GtkFrontend *inst = (GtkFrontend *)data; - static const int clips[] = { MENU_CLIPBOARD }; - term_request_copy(inst->term, clips, lenof(clips)); -} - -void paste_clipboard_menuitem(GtkMenuItem *item, gpointer data) -{ - GtkFrontend *inst = (GtkFrontend *)data; - term_request_paste(inst->term, MENU_CLIPBOARD); -} - -void copy_all_menuitem(GtkMenuItem *item, gpointer data) -{ - GtkFrontend *inst = (GtkFrontend *)data; - static const int clips[] = { COPYALL_CLIPBOARDS }; - term_copyall(inst->term, clips, lenof(clips)); -} - -void special_menuitem(GtkMenuItem *item, gpointer data) -{ - GtkFrontend *inst = (GtkFrontend *)data; - SessionSpecial *sc = g_object_get_data(G_OBJECT(item), "user-data"); - - if (inst->backend) - backend_special(inst->backend, sc->code, sc->arg); -} - -void about_menuitem(GtkMenuItem *item, gpointer data) -{ - GtkFrontend *inst = (GtkFrontend *)data; - about_box(inst->window); -} - -void event_log_menuitem(GtkMenuItem *item, gpointer data) -{ - GtkFrontend *inst = (GtkFrontend *)data; - showeventlog(inst->eventlogstuff, inst->window); -} - -void setup_clipboards(GtkFrontend *inst, Terminal *term, Conf *conf) -{ - assert(term->mouse_select_clipboards[0] == CLIP_LOCAL); - - term->n_mouse_select_clipboards = 1; - term->mouse_select_clipboards[ - term->n_mouse_select_clipboards++] = MOUSE_SELECT_CLIPBOARD; - - if (conf_get_bool(conf, CONF_mouseautocopy)) { - term->mouse_select_clipboards[ - term->n_mouse_select_clipboards++] = CLIP_CLIPBOARD; - } - - set_clipboard_atom(inst, CLIP_CUSTOM_1, GDK_NONE); - set_clipboard_atom(inst, CLIP_CUSTOM_2, GDK_NONE); - set_clipboard_atom(inst, CLIP_CUSTOM_3, GDK_NONE); - - switch (conf_get_int(conf, CONF_mousepaste)) { - case CLIPUI_IMPLICIT: - term->mouse_paste_clipboard = MOUSE_PASTE_CLIPBOARD; - break; - case CLIPUI_EXPLICIT: - term->mouse_paste_clipboard = CLIP_CLIPBOARD; - break; - case CLIPUI_CUSTOM: - term->mouse_paste_clipboard = CLIP_CUSTOM_1; - set_clipboard_atom(inst, CLIP_CUSTOM_1, - gdk_atom_intern( - conf_get_str(conf, CONF_mousepaste_custom), - false)); - break; - default: - term->mouse_paste_clipboard = CLIP_NULL; - break; - } - - if (conf_get_int(conf, CONF_ctrlshiftins) == CLIPUI_CUSTOM) { - GdkAtom atom = gdk_atom_intern( - conf_get_str(conf, CONF_ctrlshiftins_custom), false); - struct clipboard_state *state = clipboard_from_atom(inst, atom); - if (state) { - inst->clipboard_ctrlshiftins = state->clipboard; - } else { - inst->clipboard_ctrlshiftins = CLIP_CUSTOM_2; - set_clipboard_atom(inst, CLIP_CUSTOM_2, atom); - } - } - - if (conf_get_int(conf, CONF_ctrlshiftcv) == CLIPUI_CUSTOM) { - GdkAtom atom = gdk_atom_intern( - conf_get_str(conf, CONF_ctrlshiftcv_custom), false); - struct clipboard_state *state = clipboard_from_atom(inst, atom); - if (state) { - inst->clipboard_ctrlshiftins = state->clipboard; - } else { - inst->clipboard_ctrlshiftcv = CLIP_CUSTOM_3; - set_clipboard_atom(inst, CLIP_CUSTOM_3, atom); - } - } -} - -struct after_change_settings_dialog_ctx { - GtkFrontend *inst; - Conf *newconf; -}; - -static void after_change_settings_dialog(void *vctx, int retval); - -void change_settings_menuitem(GtkMenuItem *item, gpointer data) -{ - GtkFrontend *inst = (GtkFrontend *)data; - struct after_change_settings_dialog_ctx *ctx; - GtkWidget *dialog; - char *title; - - if (find_and_raise_dialog(inst, DIALOG_SLOT_RECONFIGURE)) - return; - - title = dupcat(appname, " Reconfiguration"); - - ctx = snew(struct after_change_settings_dialog_ctx); - ctx->inst = inst; - ctx->newconf = conf_copy(inst->conf); - - term_pre_reconfig(inst->term, ctx->newconf); - - dialog = create_config_box( - title, ctx->newconf, true, - inst->backend ? backend_cfg_info(inst->backend) : 0, - after_change_settings_dialog, ctx); - register_dialog(&inst->seat, DIALOG_SLOT_RECONFIGURE, dialog); - - sfree(title); -} - -static void after_change_settings_dialog(void *vctx, int retval) -{ - struct after_change_settings_dialog_ctx ctx = - *(struct after_change_settings_dialog_ctx *)vctx; - GtkFrontend *inst = ctx.inst; - Conf *oldconf = inst->conf, *newconf = ctx.newconf; - bool need_size; - - sfree(vctx); /* we've copied this already */ - - unregister_dialog(&inst->seat, DIALOG_SLOT_RECONFIGURE); - - if (retval > 0) { - inst->conf = newconf; - - /* Pass new config data to the logging module */ - log_reconfig(inst->logctx, inst->conf); - /* - * Flush the line discipline's edit buffer in the case - * where local editing has just been disabled. - */ - if (inst->ldisc) { - ldisc_configure(inst->ldisc, inst->conf); - ldisc_echoedit_update(inst->ldisc); - } - /* Pass new config data to the terminal */ - term_reconfig(inst->term, inst->conf); - setup_clipboards(inst, inst->term, inst->conf); - /* Pass new config data to the back end */ - if (inst->backend) - backend_reconfig(inst->backend, inst->conf); - - cache_conf_values(inst); - - need_size = false; - - /* - * If the scrollbar needs to be shown, hidden, or moved - * from one end to the other of the window, do so now. - */ - if (conf_get_bool(oldconf, CONF_scrollbar) != - conf_get_bool(newconf, CONF_scrollbar)) { - show_scrollbar(inst, conf_get_bool(newconf, CONF_scrollbar)); - need_size = true; - } - if (conf_get_bool(oldconf, CONF_scrollbar_on_left) != - conf_get_bool(newconf, CONF_scrollbar_on_left)) { - gtk_box_reorder_child(inst->hbox, inst->sbar, - conf_get_bool(newconf, CONF_scrollbar_on_left) - ? 0 : 1); - } - - /* - * Redo the whole tangled fonts and Unicode mess if - * necessary. - */ - if (strcmp(conf_get_fontspec(oldconf, CONF_font)->name, - conf_get_fontspec(newconf, CONF_font)->name) || - strcmp(conf_get_fontspec(oldconf, CONF_boldfont)->name, - conf_get_fontspec(newconf, CONF_boldfont)->name) || - strcmp(conf_get_fontspec(oldconf, CONF_widefont)->name, - conf_get_fontspec(newconf, CONF_widefont)->name) || - strcmp(conf_get_fontspec(oldconf, CONF_wideboldfont)->name, - conf_get_fontspec(newconf, CONF_wideboldfont)->name) || - strcmp(conf_get_str(oldconf, CONF_line_codepage), - conf_get_str(newconf, CONF_line_codepage)) || - conf_get_bool(oldconf, CONF_utf8_override) != - conf_get_bool(newconf, CONF_utf8_override) || - conf_get_int(oldconf, CONF_vtmode) != - conf_get_int(newconf, CONF_vtmode) || - conf_get_bool(oldconf, CONF_shadowbold) != - conf_get_bool(newconf, CONF_shadowbold) || - conf_get_int(oldconf, CONF_shadowboldoffset) != - conf_get_int(newconf, CONF_shadowboldoffset)) { - char *errmsg = setup_fonts_ucs(inst); - if (errmsg) { - char *msgboxtext = - dupprintf("Could not change fonts in terminal window: %s\n", - errmsg); - create_message_box( - inst->window, "Font setup error", msgboxtext, - string_width("Could not change fonts in terminal window:"), - false, &buttons_ok, trivial_post_dialog_fn, NULL); - sfree(msgboxtext); - sfree(errmsg); - } else { - need_size = true; - } - } - - /* - * Resize the window. - */ - if (conf_get_int(oldconf, CONF_width) != - conf_get_int(newconf, CONF_width) || - conf_get_int(oldconf, CONF_height) != - conf_get_int(newconf, CONF_height) || - conf_get_int(oldconf, CONF_window_border) != - conf_get_int(newconf, CONF_window_border) || - need_size) { - set_geom_hints(inst); - win_request_resize(&inst->termwin, - conf_get_int(newconf, CONF_width), - conf_get_int(newconf, CONF_height)); - } else { - /* - * The above will have caused a call to term_size() for - * us if it happened. If the user has fiddled with only - * the scrollback size, the above will not have - * happened and we will need an explicit term_size() - * here. - */ - if (conf_get_int(oldconf, CONF_savelines) != - conf_get_int(newconf, CONF_savelines)) - term_size(inst->term, inst->term->rows, inst->term->cols, - conf_get_int(newconf, CONF_savelines)); - } - - term_invalidate(inst->term); - - /* - * We do an explicit full redraw here to ensure the window - * border has been redrawn as well as the text area. - */ - gtk_widget_queue_draw(inst->area); - - conf_free(oldconf); - } else { - conf_free(newconf); - } -} - -static void change_font_size(GtkFrontend *inst, int increment) -{ - static const int conf_keys[lenof(inst->fonts)] = { - CONF_font, CONF_boldfont, CONF_widefont, CONF_wideboldfont, - }; - FontSpec *oldfonts[lenof(inst->fonts)]; - FontSpec *newfonts[lenof(inst->fonts)]; - char *errmsg = NULL; - int i; - - for (i = 0; i < lenof(newfonts); i++) - oldfonts[i] = newfonts[i] = NULL; - - for (i = 0; i < lenof(inst->fonts); i++) { - if (inst->fonts[i]) { - char *newname = unifont_size_increment(inst->fonts[i], increment); - if (!newname) - goto cleanup; - newfonts[i] = fontspec_new(newname); - sfree(newname); - } - } - - for (i = 0; i < lenof(newfonts); i++) { - if (newfonts[i]) { - oldfonts[i] = fontspec_copy( - conf_get_fontspec(inst->conf, conf_keys[i])); - conf_set_fontspec(inst->conf, conf_keys[i], newfonts[i]); - } - } - - errmsg = setup_fonts_ucs(inst); - if (errmsg) - goto cleanup; - - /* Success, so suppress putting everything back */ - for (i = 0; i < lenof(newfonts); i++) { - if (oldfonts[i]) { - fontspec_free(oldfonts[i]); - oldfonts[i] = NULL; - } - } - - set_geom_hints(inst); - win_request_resize(&inst->termwin, conf_get_int(inst->conf, CONF_width), - conf_get_int(inst->conf, CONF_height)); - term_invalidate(inst->term); - gtk_widget_queue_draw(inst->area); - - cleanup: - for (i = 0; i < lenof(oldfonts); i++) { - if (oldfonts[i]) { - conf_set_fontspec(inst->conf, conf_keys[i], oldfonts[i]); - fontspec_free(oldfonts[i]); - } - if (newfonts[i]) - fontspec_free(newfonts[i]); - } - sfree(errmsg); -} - -void dup_session_menuitem(GtkMenuItem *item, gpointer gdata) -{ - GtkFrontend *inst = (GtkFrontend *)gdata; - - launch_duplicate_session(inst->conf); -} - -void new_session_menuitem(GtkMenuItem *item, gpointer data) -{ - launch_new_session(); -} - -void restart_session_menuitem(GtkMenuItem *item, gpointer data) -{ - GtkFrontend *inst = (GtkFrontend *)data; - - if (!inst->backend) { - logevent(inst->logctx, "----- Session restarted -----"); - term_pwron(inst->term, false); - start_backend(inst); - inst->exited = false; - } -} - -void saved_session_menuitem(GtkMenuItem *item, gpointer data) -{ - char *str = (char *)g_object_get_data(G_OBJECT(item), "user-data"); - - launch_saved_session(str); -} - -void saved_session_freedata(GtkMenuItem *item, gpointer data) -{ - char *str = (char *)g_object_get_data(G_OBJECT(item), "user-data"); - - sfree(str); -} - -void app_menu_action(GtkFrontend *frontend, enum MenuAction action) -{ - GtkFrontend *inst = (GtkFrontend *)frontend; - switch (action) { - case MA_COPY: - copy_clipboard_menuitem(NULL, inst); - break; - case MA_PASTE: - paste_clipboard_menuitem(NULL, inst); - break; - case MA_COPY_ALL: - copy_all_menuitem(NULL, inst); - break; - case MA_DUPLICATE_SESSION: - dup_session_menuitem(NULL, inst); - break; - case MA_RESTART_SESSION: - restart_session_menuitem(NULL, inst); - break; - case MA_CHANGE_SETTINGS: - change_settings_menuitem(NULL, inst); - break; - case MA_CLEAR_SCROLLBACK: - clear_scrollback_menuitem(NULL, inst); - break; - case MA_RESET_TERMINAL: - reset_terminal_menuitem(NULL, inst); - break; - case MA_EVENT_LOG: - event_log_menuitem(NULL, inst); - break; - } -} - -static void update_savedsess_menu(GtkMenuItem *menuitem, gpointer data) -{ - GtkFrontend *inst = (GtkFrontend *)data; - struct sesslist sesslist; - int i; - - gtk_container_foreach(GTK_CONTAINER(inst->sessionsmenu), - (GtkCallback)gtk_widget_destroy, NULL); - - get_sesslist(&sesslist, true); - /* skip sesslist.sessions[0] == Default Settings */ - for (i = 1; i < sesslist.nsessions; i++) { - GtkWidget *menuitem = - gtk_menu_item_new_with_label(sesslist.sessions[i]); - gtk_container_add(GTK_CONTAINER(inst->sessionsmenu), menuitem); - gtk_widget_show(menuitem); - g_object_set_data(G_OBJECT(menuitem), "user-data", - dupstr(sesslist.sessions[i])); - g_signal_connect(G_OBJECT(menuitem), "activate", - G_CALLBACK(saved_session_menuitem), - inst); - g_signal_connect(G_OBJECT(menuitem), "destroy", - G_CALLBACK(saved_session_freedata), - inst); - } - if (sesslist.nsessions <= 1) { - GtkWidget *menuitem = - gtk_menu_item_new_with_label("(No sessions)"); - gtk_widget_set_sensitive(menuitem, false); - gtk_container_add(GTK_CONTAINER(inst->sessionsmenu), menuitem); - gtk_widget_show(menuitem); - } - get_sesslist(&sesslist, false); /* free up */ -} - -void set_window_icon(GtkWidget *window, const char *const *const *icon, - int n_icon) -{ -#if GTK_CHECK_VERSION(2,0,0) - GList *iconlist; - int n; -#else - GdkPixmap *iconpm; - GdkBitmap *iconmask; -#endif - - if (!n_icon) - return; - - gtk_widget_realize(window); -#if GTK_CHECK_VERSION(2,0,0) - gtk_window_set_icon(GTK_WINDOW(window), - gdk_pixbuf_new_from_xpm_data((const gchar **)icon[0])); -#else - iconpm = gdk_pixmap_create_from_xpm_d(gtk_widget_get_window(window), - &iconmask, NULL, (gchar **)icon[0]); - gdk_window_set_icon(gtk_widget_get_window(window), NULL, iconpm, iconmask); -#endif - -#if GTK_CHECK_VERSION(2,0,0) - iconlist = NULL; - for (n = 0; n < n_icon; n++) { - iconlist = - g_list_append(iconlist, - gdk_pixbuf_new_from_xpm_data((const gchar **) - icon[n])); - } - gtk_window_set_icon_list(GTK_WINDOW(window), iconlist); -#endif -} - -static void free_special_cmd(gpointer data) { sfree(data); } - -static void gtk_seat_update_specials_menu(Seat *seat) -{ - GtkFrontend *inst = container_of(seat, GtkFrontend, seat); - const SessionSpecial *specials; - - if (inst->backend) - specials = backend_get_specials(inst->backend); - else - specials = NULL; - - /* I believe this disposes of submenus too. */ - gtk_container_foreach(GTK_CONTAINER(inst->specialsmenu), - (GtkCallback)gtk_widget_destroy, NULL); - if (specials) { - int i; - GtkWidget *menu = inst->specialsmenu; - /* A lame "stack" for submenus that will do for now. */ - GtkWidget *saved_menu = NULL; - int nesting = 1; - for (i = 0; nesting > 0; i++) { - GtkWidget *menuitem = NULL; - switch (specials[i].code) { - case SS_SUBMENU: - assert (nesting < 2); - saved_menu = menu; /* XXX lame stacking */ - menu = gtk_menu_new(); - menuitem = gtk_menu_item_new_with_label(specials[i].name); - gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), menu); - gtk_container_add(GTK_CONTAINER(saved_menu), menuitem); - gtk_widget_show(menuitem); - menuitem = NULL; - nesting++; - break; - case SS_EXITMENU: - nesting--; - if (nesting) { - menu = saved_menu; /* XXX lame stacking */ - saved_menu = NULL; - } - break; - case SS_SEP: - menuitem = gtk_menu_item_new(); - break; - default: { - menuitem = gtk_menu_item_new_with_label(specials[i].name); - SessionSpecial *sc = snew(SessionSpecial); - *sc = specials[i]; /* structure copy */ - g_object_set_data_full(G_OBJECT(menuitem), "user-data", - sc, free_special_cmd); - g_signal_connect(G_OBJECT(menuitem), "activate", - G_CALLBACK(special_menuitem), inst); - break; - } - } - if (menuitem) { - gtk_container_add(GTK_CONTAINER(menu), menuitem); - gtk_widget_show(menuitem); - } - } - gtk_widget_show(inst->specialsitem1); - gtk_widget_show(inst->specialsitem2); - } else { - gtk_widget_hide(inst->specialsitem1); - gtk_widget_hide(inst->specialsitem2); - } -} - -static void start_backend(GtkFrontend *inst) -{ - const struct BackendVtable *vt; - char *error, *realhost; - - vt = select_backend(inst->conf); - - seat_set_trust_status(&inst->seat, true); - error = backend_init(vt, &inst->seat, &inst->backend, - inst->logctx, inst->conf, - conf_get_str(inst->conf, CONF_host), - conf_get_int(inst->conf, CONF_port), - &realhost, - conf_get_bool(inst->conf, CONF_tcp_nodelay), - conf_get_bool(inst->conf, CONF_tcp_keepalives)); - - if (error) { - seat_connection_fatal(&inst->seat, - "Unable to open connection to %s:\n%s", - conf_dest(inst->conf), error); - sfree(error); - inst->exited = true; - return; - } - - term_setup_window_titles(inst->term, realhost); - sfree(realhost); - - term_provide_backend(inst->term, inst->backend); - - inst->ldisc = ldisc_create(inst->conf, inst->term, inst->backend, - &inst->seat); - - gtk_widget_set_sensitive(inst->restartitem, false); -} - -#if GTK_CHECK_VERSION(2,0,0) -static void get_monitor_geometry(GtkWidget *widget, GdkRectangle *geometry) -{ -#if GTK_CHECK_VERSION(3,4,0) - GdkDisplay *display = gtk_widget_get_display(widget); - GdkWindow *gdkwindow = gtk_widget_get_window(widget); -# if GTK_CHECK_VERSION(3,22,0) - GdkMonitor *monitor; - if (gdkwindow) - monitor = gdk_display_get_monitor_at_window(display, gdkwindow); - else - monitor = gdk_display_get_monitor(display, 0); - gdk_monitor_get_geometry(monitor, geometry); -# else - GdkScreen *screen = gdk_display_get_default_screen(display); - gint monitor_num = gdk_screen_get_monitor_at_window(screen, gdkwindow); - gdk_screen_get_monitor_geometry(screen, monitor_num, geometry); -# endif -#else - geometry->x = geometry->y = 0; - geometry->width = gdk_screen_width(); - geometry->height = gdk_screen_height(); -#endif -} -#endif - -static const TermWinVtable gtk_termwin_vt = { - .setup_draw_ctx = gtkwin_setup_draw_ctx, - .draw_text = gtkwin_draw_text, - .draw_cursor = gtkwin_draw_cursor, - .draw_trust_sigil = gtkwin_draw_trust_sigil, - .char_width = gtkwin_char_width, - .free_draw_ctx = gtkwin_free_draw_ctx, - .set_cursor_pos = gtkwin_set_cursor_pos, - .set_raw_mouse_mode = gtkwin_set_raw_mouse_mode, - .set_raw_mouse_mode_pointer = gtkwin_set_raw_mouse_mode_pointer, - .set_scrollbar = gtkwin_set_scrollbar, - .bell = gtkwin_bell, - .clip_write = gtkwin_clip_write, - .clip_request_paste = gtkwin_clip_request_paste, - .refresh = gtkwin_refresh, - .request_resize = gtkwin_request_resize, - .set_title = gtkwin_set_title, - .set_icon_title = gtkwin_set_icon_title, - .set_minimised = gtkwin_set_minimised, - .set_maximised = gtkwin_set_maximised, - .move = gtkwin_move, - .set_zorder = gtkwin_set_zorder, - .palette_set = gtkwin_palette_set, - .palette_get_overrides = gtkwin_palette_get_overrides, -}; - -void new_session_window(Conf *conf, const char *geometry_string) -{ - GtkFrontend *inst; - - prepare_session(conf); - - /* - * Create an instance structure and initialise to zeroes - */ - inst = snew(GtkFrontend); - memset(inst, 0, sizeof(*inst)); -#ifdef JUST_USE_GTK_CLIPBOARD_UTF8 - inst->cdi_headtail.next = inst->cdi_headtail.prev = &inst->cdi_headtail; -#endif - inst->alt_keycode = -1; /* this one needs _not_ to be zero */ - inst->busy_status = BUSY_NOT; - inst->conf = conf; - inst->wintitle = inst->icontitle = NULL; - inst->drawtype = DRAWTYPE_DEFAULT; -#if GTK_CHECK_VERSION(3,4,0) - inst->cumulative_scroll = 0.0; -#endif - inst->drawing_area_setup_needed = true; - - inst->termwin.vt = >k_termwin_vt; - inst->seat.vt = >k_seat_vt; - inst->logpolicy.vt = >k_logpolicy_vt; - -#ifndef NOT_X_WINDOWS - inst->disp = get_x11_display(); - if (geometry_string) { - int flags, x, y; - unsigned int w, h; - flags = XParseGeometry(geometry_string, &x, &y, &w, &h); - if (flags & WidthValue) - conf_set_int(conf, CONF_width, w); - if (flags & HeightValue) - conf_set_int(conf, CONF_height, h); - - if (flags & (XValue | YValue)) { - inst->xpos = x; - inst->ypos = y; - inst->gotpos = true; - inst->gravity = ((flags & XNegative ? 1 : 0) | - (flags & YNegative ? 2 : 0)); - } - } -#endif - - if (!compound_text_atom) - compound_text_atom = gdk_atom_intern("COMPOUND_TEXT", false); - if (!utf8_string_atom) - utf8_string_atom = gdk_atom_intern("UTF8_STRING", false); - if (!clipboard_atom) - clipboard_atom = gdk_atom_intern("CLIPBOARD", false); - - inst->area = gtk_drawing_area_new(); - gtk_widget_set_name(GTK_WIDGET(inst->area), "drawing-area"); - - { - char *errmsg = setup_fonts_ucs(inst); - if (errmsg) { - window_setup_error(errmsg); - sfree(errmsg); - gtk_widget_destroy(inst->area); - sfree(inst); - return; - } - } - -#if GTK_CHECK_VERSION(2,0,0) - inst->imc = gtk_im_multicontext_new(); -#endif - - inst->window = make_gtk_toplevel_window(inst); - gtk_widget_set_name(GTK_WIDGET(inst->window), "top-level"); - { - const char *winclass = conf_get_str(inst->conf, CONF_winclass); - if (*winclass) { -#if GTK_CHECK_VERSION(3,22,0) -#ifndef NOT_X_WINDOWS - GdkWindow *gdkwin; - gtk_widget_realize(GTK_WIDGET(inst->window)); - gdkwin = gtk_widget_get_window(GTK_WIDGET(inst->window)); - if (inst->disp && gdk_window_ensure_native(gdkwin)) { - XClassHint *xch = XAllocClassHint(); - xch->res_name = (char *)winclass; - xch->res_class = (char *)winclass; - XSetClassHint(inst->disp, GDK_WINDOW_XID(gdkwin), xch); - XFree(xch); - } -#endif - /* - * If we do have NOT_X_WINDOWS set, then we don't have any - * function in GTK 3.22 equivalent to the above. But then, - * surely in that situation the deprecated - * gtk_window_set_wmclass wouldn't have done anything - * meaningful in previous GTKs either. - */ -#else - gtk_window_set_wmclass(GTK_WINDOW(inst->window), - winclass, winclass); -#endif - } - } - - inst->width = conf_get_int(inst->conf, CONF_width); - inst->height = conf_get_int(inst->conf, CONF_height); - cache_conf_values(inst); - - init_clipboard(inst); - - inst->sbar_adjust = GTK_ADJUSTMENT(gtk_adjustment_new(0,0,0,0,0,0)); - inst->sbar = gtk_vscrollbar_new(inst->sbar_adjust); - inst->hbox = GTK_BOX(gtk_hbox_new(false, 0)); - /* - * We always create the scrollbar; it remains invisible if - * unwanted, so we can pop it up quickly if it suddenly becomes - * desirable. - */ - if (conf_get_bool(inst->conf, CONF_scrollbar_on_left)) - gtk_box_pack_start(inst->hbox, inst->sbar, false, false, 0); - gtk_box_pack_start(inst->hbox, inst->area, true, true, 0); - if (!conf_get_bool(inst->conf, CONF_scrollbar_on_left)) - gtk_box_pack_start(inst->hbox, inst->sbar, false, false, 0); - - gtk_container_add(GTK_CONTAINER(inst->window), GTK_WIDGET(inst->hbox)); - - gtk_widget_show(inst->area); - show_scrollbar(inst, conf_get_bool(inst->conf, CONF_scrollbar)); - gtk_widget_show(GTK_WIDGET(inst->hbox)); - - /* - * We must call gtk_widget_realize before setting up the geometry - * hints, so that GtkApplicationWindow will have actually created - * its menu bar (if it's going to) and hence compute_geom_hints - * can find it to take its size into account. - */ - gtk_widget_realize(inst->window); - set_geom_hints(inst); - -#if GTK_CHECK_VERSION(3,0,0) - { - int wp, hp; - compute_whole_window_size(inst, inst->width, inst->height, &wp, &hp); - gtk_window_set_default_size(GTK_WINDOW(inst->window), wp, hp); - } -#else - { - int w = inst->font_width * inst->width + 2*inst->window_border; - int h = inst->font_height * inst->height + 2*inst->window_border; -#if GTK_CHECK_VERSION(2,0,0) - gtk_widget_set_size_request(inst->area, w, h); -#else - gtk_drawing_area_size(GTK_DRAWING_AREA(inst->area), w, h); -#endif - } -#endif - -#if GTK_CHECK_VERSION(2,0,0) - if (inst->gotpos) { - static const GdkGravity gravities[] = { - GDK_GRAVITY_NORTH_WEST, - GDK_GRAVITY_NORTH_EAST, - GDK_GRAVITY_SOUTH_WEST, - GDK_GRAVITY_SOUTH_EAST, - }; - int x = inst->xpos, y = inst->ypos; - int wp, hp; - GdkRectangle monitor_geometry; - compute_whole_window_size(inst, inst->width, inst->height, &wp, &hp); - get_monitor_geometry(GTK_WIDGET(inst->window), &monitor_geometry); - if (inst->gravity & 1) x += (monitor_geometry.width - wp); - if (inst->gravity & 2) y += (monitor_geometry.height - hp); - gtk_window_set_gravity(GTK_WINDOW(inst->window), - gravities[inst->gravity & 3]); - gtk_window_move(GTK_WINDOW(inst->window), x, y); - } -#else - if (inst->gotpos) { - int x = inst->xpos, y = inst->ypos; - GtkRequisition req; - gtk_widget_size_request(GTK_WIDGET(inst->window), &req); - if (inst->gravity & 1) x += gdk_screen_width() - req.width; - if (inst->gravity & 2) y += gdk_screen_height() - req.height; - gtk_window_set_position(GTK_WINDOW(inst->window), GTK_WIN_POS_NONE); - gtk_widget_set_uposition(GTK_WIDGET(inst->window), x, y); - } -#endif - - g_signal_connect(G_OBJECT(inst->window), "destroy", - G_CALLBACK(destroy), inst); - g_signal_connect(G_OBJECT(inst->window), "delete_event", - G_CALLBACK(delete_window), inst); - g_signal_connect(G_OBJECT(inst->window), "key_press_event", - G_CALLBACK(key_event), inst); - g_signal_connect(G_OBJECT(inst->window), "key_release_event", - G_CALLBACK(key_event), inst); - g_signal_connect(G_OBJECT(inst->window), "focus_in_event", - G_CALLBACK(focus_event), inst); - g_signal_connect(G_OBJECT(inst->window), "focus_out_event", - G_CALLBACK(focus_event), inst); - g_signal_connect(G_OBJECT(inst->area), "realize", - G_CALLBACK(area_realised), inst); - g_signal_connect(G_OBJECT(inst->area), "size_allocate", - G_CALLBACK(area_size_allocate), inst); - g_signal_connect(G_OBJECT(inst->window), "configure_event", - G_CALLBACK(window_configured), inst); -#if GTK_CHECK_VERSION(3,10,0) - g_signal_connect(G_OBJECT(inst->area), "configure_event", - G_CALLBACK(area_configured), inst); -#endif -#if GTK_CHECK_VERSION(3,0,0) - g_signal_connect(G_OBJECT(inst->area), "draw", - G_CALLBACK(draw_area), inst); -#else - g_signal_connect(G_OBJECT(inst->area), "expose_event", - G_CALLBACK(expose_area), inst); -#endif - g_signal_connect(G_OBJECT(inst->area), "button_press_event", - G_CALLBACK(button_event), inst); - g_signal_connect(G_OBJECT(inst->area), "button_release_event", - G_CALLBACK(button_event), inst); -#if GTK_CHECK_VERSION(2,0,0) - g_signal_connect(G_OBJECT(inst->area), "scroll_event", - G_CALLBACK(scroll_event), inst); -#endif - g_signal_connect(G_OBJECT(inst->area), "motion_notify_event", - G_CALLBACK(motion_event), inst); -#if GTK_CHECK_VERSION(2,0,0) - g_signal_connect(G_OBJECT(inst->imc), "commit", - G_CALLBACK(input_method_commit_event), inst); -#endif - if (conf_get_bool(inst->conf, CONF_scrollbar)) - g_signal_connect(G_OBJECT(inst->sbar_adjust), "value_changed", - G_CALLBACK(scrollbar_moved), inst); - gtk_widget_add_events(GTK_WIDGET(inst->area), - GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK | - GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | - GDK_POINTER_MOTION_MASK | GDK_BUTTON_MOTION_MASK -#if GTK_CHECK_VERSION(3,4,0) - | GDK_SMOOTH_SCROLL_MASK -#endif - ); - - set_window_icon(inst->window, main_icon, n_main_icon); - - gtk_widget_show(inst->window); - - set_window_background(inst); - - /* - * Set up the Ctrl+rightclick context menu. - */ - { - GtkWidget *menuitem; - char *s; - - inst->menu = gtk_menu_new(); - -#define MKMENUITEM(title, func) do \ - { \ - menuitem = gtk_menu_item_new_with_label(title); \ - gtk_container_add(GTK_CONTAINER(inst->menu), menuitem); \ - gtk_widget_show(menuitem); \ - g_signal_connect(G_OBJECT(menuitem), "activate", \ - G_CALLBACK(func), inst); \ - } while (0) - -#define MKSUBMENU(title) do \ - { \ - menuitem = gtk_menu_item_new_with_label(title); \ - gtk_container_add(GTK_CONTAINER(inst->menu), menuitem); \ - gtk_widget_show(menuitem); \ - } while (0) - -#define MKSEP() do \ - { \ - menuitem = gtk_menu_item_new(); \ - gtk_container_add(GTK_CONTAINER(inst->menu), menuitem); \ - gtk_widget_show(menuitem); \ - } while (0) - - if (new_session) - MKMENUITEM("New Session...", new_session_menuitem); - MKMENUITEM("Restart Session", restart_session_menuitem); - inst->restartitem = menuitem; - gtk_widget_set_sensitive(inst->restartitem, false); - MKMENUITEM("Duplicate Session", dup_session_menuitem); - if (saved_sessions) { - inst->sessionsmenu = gtk_menu_new(); - /* sessionsmenu will be updated when it's invoked */ - /* XXX is this the right way to do dynamic menus in Gtk? */ - MKMENUITEM("Saved Sessions", update_savedsess_menu); - gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), - inst->sessionsmenu); - } - MKSEP(); - MKMENUITEM("Change Settings...", change_settings_menuitem); - MKSEP(); - if (use_event_log) - MKMENUITEM("Event Log", event_log_menuitem); - MKSUBMENU("Special Commands"); - inst->specialsmenu = gtk_menu_new(); - gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), inst->specialsmenu); - inst->specialsitem1 = menuitem; - MKSEP(); - inst->specialsitem2 = menuitem; - gtk_widget_hide(inst->specialsitem1); - gtk_widget_hide(inst->specialsitem2); - MKMENUITEM("Clear Scrollback", clear_scrollback_menuitem); - MKMENUITEM("Reset Terminal", reset_terminal_menuitem); - MKSEP(); - MKMENUITEM("Copy to " CLIPNAME_EXPLICIT_OBJECT, - copy_clipboard_menuitem); - MKMENUITEM("Paste from " CLIPNAME_EXPLICIT_OBJECT, - paste_clipboard_menuitem); - MKMENUITEM("Copy All", copy_all_menuitem); - MKSEP(); - s = dupcat("About ", appname); - MKMENUITEM(s, about_menuitem); - sfree(s); -#undef MKMENUITEM -#undef MKSUBMENU -#undef MKSEP - } - - inst->textcursor = make_mouse_ptr(inst, GDK_XTERM); - inst->rawcursor = make_mouse_ptr(inst, GDK_LEFT_PTR); - inst->waitcursor = make_mouse_ptr(inst, GDK_WATCH); - inst->blankcursor = make_mouse_ptr(inst, -1); - inst->currcursor = inst->textcursor; - show_mouseptr(inst, true); - - inst->eventlogstuff = eventlogstuff_new(); - - inst->term = term_init(inst->conf, &inst->ucsdata, &inst->termwin); - setup_clipboards(inst, inst->term, inst->conf); - inst->logctx = log_init(&inst->logpolicy, inst->conf); - term_provide_logctx(inst->term, inst->logctx); - - term_size(inst->term, inst->height, inst->width, - conf_get_int(inst->conf, CONF_savelines)); - -#if GTK_CHECK_VERSION(2,0,0) - /* Delay this signal connection until after inst->term exists */ - g_signal_connect(G_OBJECT(inst->window), "window_state_event", - G_CALLBACK(window_state_event), inst); -#endif - - inst->exited = false; - - start_backend(inst); - - if (inst->ldisc) /* early backend failure might make this NULL already */ - ldisc_echoedit_update(inst->ldisc); /* cause ldisc to notice changes */ -} - -static bool gtk_seat_set_trust_status(Seat *seat, bool trusted) -{ - GtkFrontend *inst = container_of(seat, GtkFrontend, seat); - term_set_trust_status(inst->term, trusted); - return true; -} - -static bool gtk_seat_get_cursor_position(Seat *seat, int *x, int *y) -{ - GtkFrontend *inst = container_of(seat, GtkFrontend, seat); - if (inst->term) { - term_get_cursor_position(inst->term, x, y); - return true; - } - return false; -} |