2 * gtkapp.c: a top-level front end to GUI PuTTY and pterm, using
3 * GtkApplication. Suitable for OS X. Currently unfinished.
5 * (You could run it on ordinary Linux GTK too, in principle, but I
6 * don't think it would be particularly useful to do so, even once
12 To build on OS X, you will need a build environment with GTK 3 and
13 gtk-mac-bundler, and also Halibut on the path (to build the man pages,
14 without which the standard Makefile will complain). Then, from a clean
17 ./mkfiles.pl -U --with-quartz
22 and you should get unix/PuTTY.app and unix/PTerm.app as output.
28 TODO list for a sensible GTK3 PuTTY/pterm on OS X:
30 Menu items' keyboard shortcuts (Command-Q for Quit, Command-V for
31 Paste) do not currently work. It's intentional that if you turn on
32 'Command key acts as Meta' in the configuration then those shortcuts
33 should be superseded by the Meta-key functionality (e.g. Cmd-Q should
34 send ESC Q to the session), for the benefit of people whose non-Mac
35 keyboard reflexes expect the Meta key to be in that position; but if
36 you don't turn that option on, then these shortcuts should work as an
37 ordinary Mac user expects, and currently they don't.
39 Windows don't close sensibly when their sessions terminate. This is
40 because until now I've relied on calling cleanup_exit() or
41 gtk_main_quit() in gtkwin.c to terminate the program, which is
42 conceptually wrong in this situation (we don't want to quit the whole
43 application when just one window closes) and also doesn't reliably
44 work anyway (GtkApplication doesn't seem to have a gtk_main invocation
45 in it at all, so those calls to gtk_main_quit produce a GTK assertion
46 failure message on standard error). Need to introduce a proper 'clean
47 up this struct gui_data' function (including finalising other stuff
48 dangling off it like the backend), call that, and delete just that one
49 window. (And then work out a replacement mechanism for having the
50 ordinary Unix-style gtkmain.c based programs terminate when their
51 session does.) connection_fatal() in particular should invoke this
52 mechanism, and terminate just the connection that had trouble.
54 Mouse wheel events and trackpad scrolling gestures don't work quite
55 right in the terminal drawing area.
57 There doesn't seem to be a resize handle on terminal windows. I don't
58 think this is a fundamental limitation of OS X GTK (their demo app has
59 one), so perhaps I need to do something to make sure it appears?
61 A slight oddity with menus that pop up directly under the mouse
62 pointer: mousing over the menu items doesn't highlight them initially,
63 but if I mouse off the menu and back on (without un-popping-it-up)
64 then suddenly that does work. I don't know if this is something I can
65 fix, though; it might very well be a quirk of the underlying GTK.
67 I want to arrange *some* way to paste efficiently using my Apple
68 wireless keyboard and trackpad. The trackpad doesn't provide a middle
69 button; I can't use the historic Shift-Ins shortcut because the
70 keyboard has no Ins key; I configure the Command key to be Meta, so
71 Command-V is off the table too. I can always use the menu, but I'd
72 prefer there to be _some_ easily reachable mouse or keyboard gesture.
74 Revamping the clipboard handling in general is going to be needed, as
75 well. Not everybody will want the current auto-copy-on-select
76 behaviour inherited from ordinary Unix PuTTY. Should arrange to have a
77 mode in which you have to take an explicit Copy action, and then
78 arrange that the Edit menu includes one of those.
80 Dialog boxes shouldn't be modal. I think this is a good policy change
81 in general, and the required infrastructure changes will benefit the
82 Windows front end too, but for a multi-session process it's even more
83 critical - you need to be able to type into one session window while
84 setting up the configuration for launching another. So everywhere we
85 currently run a sub-instance of gtk_main, or call any API function
86 that implicitly does that (like gtk_dialog_run), we should switch to
87 putting up the dialog box and going back to our ordinary main loop,
88 and whatever we were going to do after the dialog closed we should
89 remember to do it when that happens later on. Also then we can remove
90 the post_main() horror from gtkcomm.c.
92 The application menu bar is very minimal at the moment. Should include
93 all the usual stuff from the Ctrl-right-click menu - saved sessions,
94 mid-session special commands, Duplicate Session, Change Settings,
95 Event Log, clear scrollback, reset terminal, about box, anything else
98 Does OS X have a standard system of online help that I could tie into?
100 Need to work out what if anything we can do with Pageant on OS X.
101 Perhaps it's too much bother and we should just talk to the
102 system-provided SSH agent? Or perhaps not.
104 Nice-to-have: a custom right-click menu from the application's dock
105 tile, listing the saved sessions for quick launch. As far as I know
106 there's nothing built in to GtkApplication that can produce this, but
107 it's possible we might be able to drop a piece of native Cocoa code in
108 under ifdef, substituting an application delegate of our own which
109 forwards all methods we're not interested in to the GTK-provided one?
111 At the point where this becomes polished enough to publish pre-built,
112 I suppose I'll have to look into OS X code signing.
113 https://wiki.gnome.org/Projects/GTK%2B/OSX/Bundling has some links.
124 #define MAY_REFER_TO_GTK_IN_HEADERS
128 char *x_get_default(const char *key) { return NULL; }
130 #if !GTK_CHECK_VERSION(3,0,0)
131 /* This front end only works in GTK 3. If that's not what we've got,
132 * it's easier to just turn this program into a trivial stub by ifdef
133 * in the source than it is to remove it in the makefile edifice. */
134 int main(int argc, char **argv)
136 fprintf(stderr, "launcher does nothing on non-OSX platforms\n");
139 GtkWidget *make_gtk_toplevel_window(void *frontend) { return NULL; }
140 void launch_duplicate_session(Conf *conf) {}
141 void launch_new_session(void) {}
142 void launch_saved_session(const char *str) {}
143 #else /* GTK_CHECK_VERSION(3,0,0) */
145 static void startup(GApplication *app, gpointer user_data)
147 GMenu *menubar, *menu, *section;
149 menubar = g_menu_new();
152 g_menu_append_submenu(menubar, "File", G_MENU_MODEL(menu));
154 section = g_menu_new();
155 g_menu_append_section(menu, NULL, G_MENU_MODEL(section));
156 g_menu_append(section, "New Window", "app.newwin");
159 g_menu_append_submenu(menubar, "Edit", G_MENU_MODEL(menu));
161 section = g_menu_new();
162 g_menu_append_section(menu, NULL, G_MENU_MODEL(section));
163 g_menu_append(section, "Paste", "win.paste");
165 gtk_application_set_menubar(GTK_APPLICATION(app),
166 G_MENU_MODEL(menubar));
169 static void paste_cb(GSimpleAction *action,
173 request_paste(user_data);
176 static const GActionEntry win_actions[] = {
177 { "paste", paste_cb },
180 static GtkApplication *app;
181 GtkWidget *make_gtk_toplevel_window(void *frontend)
183 GtkWidget *win = gtk_application_window_new(app);
184 g_action_map_add_action_entries(G_ACTION_MAP(win),
186 G_N_ELEMENTS(win_actions),
191 extern int cfgbox(Conf *conf);
193 void launch_duplicate_session(Conf *conf)
195 extern const int dup_check_launchable;
196 assert(!dup_check_launchable || conf_launchable(conf));
197 new_session_window(conf, NULL);
200 void launch_new_session(void)
202 Conf *conf = conf_new();
203 do_defaults(NULL, conf);
204 if (conf_launchable(conf) || cfgbox(conf)) {
205 new_session_window(conf, NULL);
209 void launch_saved_session(const char *str)
211 Conf *conf = conf_new();
212 do_defaults(str, conf);
213 if (conf_launchable(conf) || cfgbox(conf)) {
214 new_session_window(conf, NULL);
218 void new_app_win(GtkApplication *app)
220 launch_new_session();
223 static void activate(GApplication *app,
226 new_app_win(GTK_APPLICATION(app));
229 static void newwin_cb(GSimpleAction *action,
233 new_app_win(GTK_APPLICATION(user_data));
236 static void quit_cb(GSimpleAction *action,
240 g_application_quit(G_APPLICATION(user_data));
243 static const GActionEntry app_actions[] = {
244 { "newwin", newwin_cb },
248 int main(int argc, char **argv)
253 /* Call the function in ux{putty,pterm}.c to do app-type
255 extern void setup(int);
256 setup(FALSE); /* FALSE means we are not a one-session process */
260 extern char *pty_osx_envrestore_prefix;
261 pty_osx_envrestore_prefix = argv[--argc];
265 const char *home = getenv("HOME");
273 app = gtk_application_new("org.tartarus.projects.putty.macputty",
274 G_APPLICATION_FLAGS_NONE);
275 g_signal_connect(app, "activate", G_CALLBACK(activate), NULL);
276 g_signal_connect(app, "startup", G_CALLBACK(startup), NULL);
277 g_action_map_add_action_entries(G_ACTION_MAP(app),
279 G_N_ELEMENTS(app_actions),
282 status = g_application_run(G_APPLICATION(app), argc, argv);
288 #endif /* GTK_CHECK_VERSION(3,0,0) */