]> asedeno.scripts.mit.edu Git - PuTTY.git/blob - unix/gtkapp.c
249be2d39269fea334b2743c1835c76854462455
[PuTTY.git] / unix / gtkapp.c
1 /*
2  * gtkapp.c: a top-level front end to GUI PuTTY and pterm, using
3  * GtkApplication. Suitable for OS X. Currently unfinished.
4  *
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
7  * it's fully working.)
8  */
9
10 /*
11
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
15 checkout, do this:
16
17 ./mkfiles.pl -U --with-quartz
18 make -C icons icns
19 make -C doc
20 make
21
22 and you should get unix/PuTTY.app and unix/PTerm.app as output.
23
24 */
25
26 /*
27
28 TODO list for a sensible GTK3 PuTTY/pterm on OS X:
29
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.
38
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.
53
54 Mouse wheel events and trackpad scrolling gestures don't work quite
55 right in the terminal drawing area.
56
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?
60
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.
66
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.
73
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.
79
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.
91
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
96 I can think of.
97
98 Does OS X have a standard system of online help that I could tie into?
99
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.
103
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?
110
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.
114
115  */
116
117 #include <assert.h>
118 #include <stdlib.h>
119
120 #include <unistd.h>
121
122 #include <gtk/gtk.h>
123
124 #define MAY_REFER_TO_GTK_IN_HEADERS
125
126 #include "putty.h"
127
128 char *x_get_default(const char *key) { return NULL; }
129
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)
135 {
136     fprintf(stderr, "launcher does nothing on non-OSX platforms\n");
137     return 1;
138 }
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) */
144
145 static void startup(GApplication *app, gpointer user_data)
146 {
147     GMenu *menubar, *menu, *section;
148
149     menubar = g_menu_new();
150
151     menu = g_menu_new();
152     g_menu_append_submenu(menubar, "File", G_MENU_MODEL(menu));
153
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");
157
158     menu = g_menu_new();
159     g_menu_append_submenu(menubar, "Edit", G_MENU_MODEL(menu));
160
161     section = g_menu_new();
162     g_menu_append_section(menu, NULL, G_MENU_MODEL(section));
163     g_menu_append(section, "Paste", "win.paste");
164
165     gtk_application_set_menubar(GTK_APPLICATION(app),
166                                 G_MENU_MODEL(menubar));
167 }
168
169 static void paste_cb(GSimpleAction *action,
170                      GVariant      *parameter,
171                      gpointer       user_data)
172 {
173     request_paste(user_data);
174 }
175
176 static const GActionEntry win_actions[] = {
177     { "paste", paste_cb },
178 };
179
180 static GtkApplication *app;
181 GtkWidget *make_gtk_toplevel_window(void *frontend)
182 {
183     GtkWidget *win = gtk_application_window_new(app);
184     g_action_map_add_action_entries(G_ACTION_MAP(win),
185                                     win_actions,
186                                     G_N_ELEMENTS(win_actions),
187                                     frontend);
188     return win;
189 }
190
191 extern int cfgbox(Conf *conf);
192
193 void launch_duplicate_session(Conf *conf)
194 {
195     extern const int dup_check_launchable;
196     assert(!dup_check_launchable || conf_launchable(conf));
197     new_session_window(conf, NULL);
198 }
199
200 void launch_new_session(void)
201 {
202     Conf *conf = conf_new();
203     do_defaults(NULL, conf);
204     if (conf_launchable(conf) || cfgbox(conf)) {
205         new_session_window(conf, NULL);
206     }
207 }
208
209 void launch_saved_session(const char *str)
210 {
211     Conf *conf = conf_new();
212     do_defaults(str, conf);
213     if (conf_launchable(conf) || cfgbox(conf)) {
214         new_session_window(conf, NULL);
215     }
216 }
217
218 void new_app_win(GtkApplication *app)
219 {
220     launch_new_session();
221 }
222
223 static void activate(GApplication *app,
224                      gpointer      user_data)
225 {
226     new_app_win(GTK_APPLICATION(app));
227 }
228
229 static void newwin_cb(GSimpleAction *action,
230                       GVariant      *parameter,
231                       gpointer       user_data)
232 {
233     new_app_win(GTK_APPLICATION(user_data));
234 }
235
236 static void quit_cb(GSimpleAction *action,
237                     GVariant      *parameter,
238                     gpointer       user_data)
239 {
240     g_application_quit(G_APPLICATION(user_data));
241 }
242
243 static const GActionEntry app_actions[] = {
244     { "newwin", newwin_cb },
245     { "quit", quit_cb },
246 };
247
248 int main(int argc, char **argv)
249 {
250     int status;
251
252     {
253         /* Call the function in ux{putty,pterm}.c to do app-type
254          * specific setup */
255         extern void setup(int);
256         setup(FALSE);     /* FALSE means we are not a one-session process */
257     }
258
259     if (argc > 1) {
260         extern char *pty_osx_envrestore_prefix;
261         pty_osx_envrestore_prefix = argv[--argc];
262     }
263
264     {
265         const char *home = getenv("HOME");
266         if (home) {
267             if (chdir(home)) {}
268         }
269     }
270
271     gtkcomm_setup();
272
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),
278                                     app_actions,
279                                     G_N_ELEMENTS(app_actions),
280                                     app);
281
282     status = g_application_run(G_APPLICATION(app), argc, argv);
283     g_object_unref(app);
284
285     return status;
286 }
287
288 #endif /* GTK_CHECK_VERSION(3,0,0) */