mirror of
https://git.tartarus.org/simon/putty.git
synced 2025-01-10 01:48:00 +00:00
c19e7215dd
This brings various concrete advantages over the previous system: - consistent support for out-of-tree builds on all platforms - more thorough support for Visual Studio IDE project files - support for Ninja-based builds, which is particularly useful on Windows where the alternative nmake has no parallel option - a really simple set of build instructions that work the same way on all the major platforms (look how much shorter README is!) - better decoupling of the project configuration from the toolchain configuration, so that my Windows cross-building doesn't need (much) special treatment in CMakeLists.txt - configure-time tests on Windows as well as Linux, so that a lot of ad-hoc #ifdefs second-guessing a particular feature's presence from the compiler version can now be replaced by tests of the feature itself Also some longer-term software-engineering advantages: - other people have actually heard of CMake, so they'll be able to produce patches to the new build setup more easily - unlike the old mkfiles.pl, CMake is not my personal problem to maintain - most importantly, mkfiles.pl was just a horrible pile of unmaintainable cruft, which even I found it painful to make changes to or to use, and desperately needed throwing in the bin. I've already thrown away all the variants of it I had in other projects of mine, and was only delaying this one so we could make the 0.75 release branch first. This change comes with a noticeable build-level restructuring. The previous Recipe worked by compiling every object file exactly once, and then making each executable by linking a precisely specified subset of the same object files. But in CMake, that's not the natural way to work - if you write the obvious command that puts the same source file into two executable targets, CMake generates a makefile that compiles it once per target. That can be an advantage, because it gives you the freedom to compile it differently in each case (e.g. with a #define telling it which program it's part of). But in a project that has many executable targets and had carefully contrived to _never_ need to build any module more than once, all it does is bloat the build time pointlessly! To avoid slowing down the build by a large factor, I've put most of the modules of the code base into a collection of static libraries organised vaguely thematically (SSH, other backends, crypto, network, ...). That means all those modules can still be compiled just once each, because once each library is built it's reused unchanged for all the executable targets. One upside of this library-based structure is that now I don't have to manually specify exactly which objects go into which programs any more - it's enough to specify which libraries are needed, and the linker will figure out the fine detail automatically. So there's less maintenance to do in CMakeLists.txt when the source code changes. But that reorganisation also adds fragility, because of the trad Unix linker semantics of walking along the library list once each, so that cyclic references between your libraries will provoke link errors. The current setup builds successfully, but I suspect it only just manages it. (In particular, I've found that MinGW is the most finicky on this score of the Windows compilers I've tried building with. So I've included a MinGW test build in the new-look Buildscr, because otherwise I think there'd be a significant risk of introducing MinGW-only build failures due to library search order, which wasn't a risk in the previous library-free build organisation.) In the longer term I hope to be able to reduce the risk of that, via gradual reorganisation (in particular, breaking up too-monolithic modules, to reduce the risk of knock-on references when you included a module for function A and it also contains function B with an unsatisfied dependency you didn't really need). Ideally I want to reach a state in which the libraries all have sensibly described purposes, a clearly documented (partial) order in which they're permitted to depend on each other, and a specification of what stubs you have to put where if you're leaving one of them out (e.g. nocrypto) and what callbacks you have to define in your non-library objects to satisfy dependencies from things low in the stack (e.g. out_of_memory()). One thing that's gone completely missing in this migration, unfortunately, is the unfinished MacOS port linked against Quartz GTK. That's because it turned out that I can't currently build it myself, on my own Mac: my previous installation of GTK had bit-rotted as a side effect of an Xcode upgrade, and I haven't yet been able to persuade jhbuild to make me a new one. So I can't even build the MacOS port with the _old_ makefiles, and hence, I have no way of checking that the new ones also work. I hope to bring that port back to life at some point, but I don't want it to block the rest of this change.
341 lines
11 KiB
C
341 lines
11 KiB
C
/*
|
|
* gtkapp.c: a top-level front end to GUI PuTTY and pterm, using
|
|
* GtkApplication. Suitable for OS X. Currently unfinished.
|
|
*
|
|
* (You could run it on ordinary Linux GTK too, in principle, but I
|
|
* don't think it would be particularly useful to do so, even once
|
|
* it's fully working.)
|
|
*/
|
|
|
|
/*
|
|
|
|
Building this for OS X is currently broken, because the new
|
|
CMake-based build system doesn't support it yet. Probably what needs
|
|
doing is to add it back in to unix/CMakeLists.txt under a condition
|
|
like if(CMAKE_SYSTEM_NAME MATCHES "Darwin").
|
|
|
|
*/
|
|
|
|
/*
|
|
|
|
TODO list for a sensible GTK3 PuTTY/pterm on OS X:
|
|
|
|
Still to do on the application menu bar: items that have to vary with
|
|
context or user action (saved sessions and mid-session special
|
|
commands), and disabling/enabling the main actions in parallel with
|
|
their counterparts in the Ctrl-rightclick context menu.
|
|
|
|
Mouse wheel events and trackpad scrolling gestures don't work quite
|
|
right in the terminal drawing area. This seems to be a combination of
|
|
two things, neither of which I completely understand yet. Firstly, on
|
|
OS X GTK my trackpad seems to generate GDK scroll events for which
|
|
gdk_event_get_scroll_deltas returns integers rather than integer
|
|
multiples of 1/30, so we end up scrolling by very large amounts;
|
|
secondly, the window doesn't seem to receive a GTK "draw" event until
|
|
after the entire scroll gesture is complete, which means we don't get
|
|
constant visual feedback on how much we're scrolling by.
|
|
|
|
There doesn't seem to be a resize handle on terminal windows. Then
|
|
again, they do seem to _be_ resizable; the handle just isn't shown.
|
|
Perhaps that's a feature (certainly in a scrollbarless configuration
|
|
the handle gets in the way of the bottom right character cell in the
|
|
terminal itself), but it would be nice to at least understand _why_ it
|
|
happens and perhaps include an option to put it back again.
|
|
|
|
A slight oddity with menus that pop up directly under the mouse
|
|
pointer: mousing over the menu items doesn't highlight them initially,
|
|
but if I mouse off the menu and back on (without un-popping-it-up)
|
|
then suddenly that does work. I don't know if this is something I can
|
|
fix, though; it might very well be a quirk of the underlying GTK.
|
|
|
|
Does OS X have a standard system of online help that I could tie into?
|
|
|
|
Need to work out what if anything we can do with Pageant on OS X.
|
|
Perhaps it's too much bother and we should just talk to the
|
|
system-provided SSH agent? Or perhaps not.
|
|
|
|
Nice-to-have: a custom right-click menu from the application's dock
|
|
tile, listing the saved sessions for quick launch. As far as I know
|
|
there's nothing built in to GtkApplication that can produce this, but
|
|
it's possible we might be able to drop a piece of native Cocoa code in
|
|
under ifdef, substituting an application delegate of our own which
|
|
forwards all methods we're not interested in to the GTK-provided one?
|
|
|
|
At the point where this becomes polished enough to publish pre-built,
|
|
I suppose I'll have to look into OS X code signing.
|
|
https://wiki.gnome.org/Projects/GTK%2B/OSX/Bundling has some links.
|
|
|
|
*/
|
|
|
|
#include <assert.h>
|
|
#include <stdlib.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <gtk/gtk.h>
|
|
|
|
#define MAY_REFER_TO_GTK_IN_HEADERS
|
|
|
|
#include "putty.h"
|
|
#include "gtkmisc.h"
|
|
|
|
char *x_get_default(const char *key) { return NULL; }
|
|
|
|
const bool buildinfo_gtk_relevant = true;
|
|
|
|
#if !GTK_CHECK_VERSION(3,0,0)
|
|
/* This front end only works in GTK 3. If that's not what we've got,
|
|
* it's easier to just turn this program into a trivial stub by ifdef
|
|
* in the source than it is to remove it in the makefile edifice. */
|
|
int main(int argc, char **argv)
|
|
{
|
|
fprintf(stderr, "GtkApplication frontend doesn't work pre-GTK3\n");
|
|
return 1;
|
|
}
|
|
GtkWidget *make_gtk_toplevel_window(GtkFrontend *frontend) { return NULL; }
|
|
void launch_duplicate_session(Conf *conf) {}
|
|
void launch_new_session(void) {}
|
|
void launch_saved_session(const char *str) {}
|
|
void session_window_closed(void) {}
|
|
void window_setup_error(const char *errmsg) {}
|
|
#else /* GTK_CHECK_VERSION(3,0,0) */
|
|
|
|
static void startup(GApplication *app, gpointer user_data)
|
|
{
|
|
GMenu *menubar, *menu, *section;
|
|
|
|
menubar = g_menu_new();
|
|
|
|
menu = g_menu_new();
|
|
g_menu_append_submenu(menubar, "File", G_MENU_MODEL(menu));
|
|
|
|
section = g_menu_new();
|
|
g_menu_append_section(menu, NULL, G_MENU_MODEL(section));
|
|
g_menu_append(section, "New Window", "app.newwin");
|
|
|
|
menu = g_menu_new();
|
|
g_menu_append_submenu(menubar, "Edit", G_MENU_MODEL(menu));
|
|
|
|
section = g_menu_new();
|
|
g_menu_append_section(menu, NULL, G_MENU_MODEL(section));
|
|
g_menu_append(section, "Copy", "win.copy");
|
|
g_menu_append(section, "Paste", "win.paste");
|
|
g_menu_append(section, "Copy All", "win.copyall");
|
|
|
|
menu = g_menu_new();
|
|
g_menu_append_submenu(menubar, "Window", G_MENU_MODEL(menu));
|
|
|
|
section = g_menu_new();
|
|
g_menu_append_section(menu, NULL, G_MENU_MODEL(section));
|
|
g_menu_append(section, "Restart Session", "win.restart");
|
|
g_menu_append(section, "Duplicate Session", "win.duplicate");
|
|
|
|
section = g_menu_new();
|
|
g_menu_append_section(menu, NULL, G_MENU_MODEL(section));
|
|
g_menu_append(section, "Change Settings", "win.changesettings");
|
|
|
|
if (use_event_log) {
|
|
section = g_menu_new();
|
|
g_menu_append_section(menu, NULL, G_MENU_MODEL(section));
|
|
g_menu_append(section, "Event Log", "win.eventlog");
|
|
}
|
|
|
|
section = g_menu_new();
|
|
g_menu_append_section(menu, NULL, G_MENU_MODEL(section));
|
|
g_menu_append(section, "Clear Scrollback", "win.clearscrollback");
|
|
g_menu_append(section, "Reset Terminal", "win.resetterm");
|
|
|
|
#if GTK_CHECK_VERSION(3,12,0)
|
|
#define SET_ACCEL(app, command, accel) do \
|
|
{ \
|
|
static const char *const accels[] = { accel, NULL }; \
|
|
gtk_application_set_accels_for_action( \
|
|
GTK_APPLICATION(app), command, accels); \
|
|
} while (0)
|
|
#else
|
|
/* The Gtk function used above was new in 3.12; the one below
|
|
* was deprecated from 3.14. */
|
|
#define SET_ACCEL(app, command, accel) \
|
|
gtk_application_add_accelerator(GTK_APPLICATION(app), accel, \
|
|
command, NULL)
|
|
#endif
|
|
|
|
SET_ACCEL(app, "app.newwin", "<Primary>n");
|
|
SET_ACCEL(app, "win.copy", "<Primary>c");
|
|
SET_ACCEL(app, "win.paste", "<Primary>v");
|
|
|
|
#undef SET_ACCEL
|
|
|
|
gtk_application_set_menubar(GTK_APPLICATION(app),
|
|
G_MENU_MODEL(menubar));
|
|
}
|
|
|
|
#define WIN_ACTION_LIST(X) \
|
|
X("copy", MA_COPY) \
|
|
X("paste", MA_PASTE) \
|
|
X("copyall", MA_COPY_ALL) \
|
|
X("duplicate", MA_DUPLICATE_SESSION) \
|
|
X("restart", MA_RESTART_SESSION) \
|
|
X("changesettings", MA_CHANGE_SETTINGS) \
|
|
X("clearscrollback", MA_CLEAR_SCROLLBACK) \
|
|
X("resetterm", MA_RESET_TERMINAL) \
|
|
X("eventlog", MA_EVENT_LOG) \
|
|
/* end of list */
|
|
|
|
#define WIN_ACTION_CALLBACK(name, id) \
|
|
static void win_action_cb_ ## id(GSimpleAction *a, GVariant *p, gpointer d) \
|
|
{ app_menu_action(d, id); }
|
|
WIN_ACTION_LIST(WIN_ACTION_CALLBACK)
|
|
#undef WIN_ACTION_CALLBACK
|
|
|
|
static const GActionEntry win_actions[] = {
|
|
#define WIN_ACTION_ENTRY(name, id) { name, win_action_cb_ ## id },
|
|
WIN_ACTION_LIST(WIN_ACTION_ENTRY)
|
|
#undef WIN_ACTION_ENTRY
|
|
};
|
|
|
|
static GtkApplication *app;
|
|
GtkWidget *make_gtk_toplevel_window(GtkFrontend *frontend)
|
|
{
|
|
GtkWidget *win = gtk_application_window_new(app);
|
|
g_action_map_add_action_entries(G_ACTION_MAP(win),
|
|
win_actions,
|
|
G_N_ELEMENTS(win_actions),
|
|
frontend);
|
|
return win;
|
|
}
|
|
|
|
void launch_duplicate_session(Conf *conf)
|
|
{
|
|
assert(!dup_check_launchable || conf_launchable(conf));
|
|
g_application_hold(G_APPLICATION(app));
|
|
new_session_window(conf_copy(conf), NULL);
|
|
}
|
|
|
|
void session_window_closed(void)
|
|
{
|
|
g_application_release(G_APPLICATION(app));
|
|
}
|
|
|
|
static void post_initial_config_box(void *vctx, int result)
|
|
{
|
|
Conf *conf = (Conf *)vctx;
|
|
|
|
if (result > 0) {
|
|
new_session_window(conf, NULL);
|
|
} else if (result == 0) {
|
|
conf_free(conf);
|
|
g_application_release(G_APPLICATION(app));
|
|
}
|
|
}
|
|
|
|
void launch_saved_session(const char *str)
|
|
{
|
|
Conf *conf = conf_new();
|
|
do_defaults(str, conf);
|
|
|
|
g_application_hold(G_APPLICATION(app));
|
|
|
|
if (!conf_launchable(conf)) {
|
|
initial_config_box(conf, post_initial_config_box, conf);
|
|
} else {
|
|
new_session_window(conf, NULL);
|
|
}
|
|
}
|
|
|
|
void launch_new_session(void)
|
|
{
|
|
/* Same as launch_saved_session except that we pass NULL to
|
|
* do_defaults. */
|
|
launch_saved_session(NULL);
|
|
}
|
|
|
|
void new_app_win(GtkApplication *app)
|
|
{
|
|
launch_new_session();
|
|
}
|
|
|
|
static void window_setup_error_callback(void *vctx, int result)
|
|
{
|
|
g_application_release(G_APPLICATION(app));
|
|
}
|
|
|
|
void window_setup_error(const char *errmsg)
|
|
{
|
|
create_message_box(NULL, "Error creating session window", errmsg,
|
|
string_width("Some sort of fiddly error message that "
|
|
"might be technical"),
|
|
true, &buttons_ok, window_setup_error_callback, NULL);
|
|
}
|
|
|
|
static void activate(GApplication *app,
|
|
gpointer user_data)
|
|
{
|
|
new_app_win(GTK_APPLICATION(app));
|
|
}
|
|
|
|
static void newwin_cb(GSimpleAction *action,
|
|
GVariant *parameter,
|
|
gpointer user_data)
|
|
{
|
|
new_app_win(GTK_APPLICATION(user_data));
|
|
}
|
|
|
|
static void quit_cb(GSimpleAction *action,
|
|
GVariant *parameter,
|
|
gpointer user_data)
|
|
{
|
|
g_application_quit(G_APPLICATION(user_data));
|
|
}
|
|
|
|
static void about_cb(GSimpleAction *action,
|
|
GVariant *parameter,
|
|
gpointer user_data)
|
|
{
|
|
about_box(NULL);
|
|
}
|
|
|
|
static const GActionEntry app_actions[] = {
|
|
{ "newwin", newwin_cb },
|
|
{ "about", about_cb },
|
|
{ "quit", quit_cb },
|
|
};
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
int status;
|
|
|
|
/* Call the function in ux{putty,pterm}.c to do app-type
|
|
* specific setup */
|
|
setup(false); /* false means we are not a one-session process */
|
|
|
|
if (argc > 1) {
|
|
pty_osx_envrestore_prefix = argv[--argc];
|
|
}
|
|
|
|
{
|
|
const char *home = getenv("HOME");
|
|
if (home) {
|
|
if (chdir(home)) {}
|
|
}
|
|
}
|
|
|
|
gtkcomm_setup();
|
|
|
|
app = gtk_application_new("org.tartarus.projects.putty.macputty",
|
|
G_APPLICATION_FLAGS_NONE);
|
|
g_signal_connect(app, "activate", G_CALLBACK(activate), NULL);
|
|
g_signal_connect(app, "startup", G_CALLBACK(startup), NULL);
|
|
g_action_map_add_action_entries(G_ACTION_MAP(app),
|
|
app_actions,
|
|
G_N_ELEMENTS(app_actions),
|
|
app);
|
|
|
|
status = g_application_run(G_APPLICATION(app), argc, argv);
|
|
g_object_unref(app);
|
|
|
|
return status;
|
|
}
|
|
|
|
#endif /* GTK_CHECK_VERSION(3,0,0) */
|