deploy_dialog.vala 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555
  1. /*
  2. * Copyright (c) 2012-2026 Daniele Bartolini et al.
  3. * SPDX-License-Identifier: GPL-3.0-or-later
  4. */
  5. namespace Crown
  6. {
  7. public InputEnum make_deploy_config_combo()
  8. {
  9. string? labels[] =
  10. {
  11. TargetConfig.RELEASE.to_label(),
  12. TargetConfig.DEVELOPMENT.to_label(),
  13. #if CROWN_DEBUG
  14. TargetConfig.DEBUG.to_label(),
  15. #endif
  16. };
  17. string? ids[] =
  18. {
  19. ((int)TargetConfig.RELEASE).to_string(),
  20. ((int)TargetConfig.DEVELOPMENT).to_string(),
  21. #if CROWN_DEBUG
  22. ((int)TargetConfig.DEBUG).to_string(),
  23. #endif
  24. };
  25. return new InputEnum(ids[0], labels, ids);
  26. }
  27. public Gtk.Button make_deploy_button(TargetPlatform platform)
  28. {
  29. var btn = new Gtk.Button();
  30. btn.label = "Package Project for %s".printf(platform.to_label());
  31. btn.margin_start = 12;
  32. btn.margin_end = 12;
  33. btn.margin_top = 12;
  34. btn.get_style_context().add_class("suggested-action");
  35. return btn;
  36. }
  37. public delegate int DeployerCheckConfig();
  38. public class DeployerPage : Gtk.Box
  39. {
  40. public Gtk.Stack _stack;
  41. public Gtk.Box _check_config_box;
  42. public Gtk.Widget _deployer_options;
  43. public unowned DeployerCheckConfig _check_config;
  44. public DeployerPage(TargetPlatform target_platform, Gtk.Widget deployer_options, DeployerCheckConfig? check_config = null)
  45. {
  46. Object(orientation: Gtk.Orientation.VERTICAL);
  47. _stack = new Gtk.Stack();
  48. _deployer_options = deployer_options;
  49. _check_config = check_config;
  50. string h1 = "<span font_weight=\"bold\" size=\"x-large\">Not configured</span>";
  51. string p1 = "The %s deployer has not been configured yet".printf(target_platform.to_label());
  52. string p2 = "Follow <a href=\"\">the instructions</a> to get started";
  53. var h1l = new Gtk.Label(null);
  54. h1l.set_markup(h1);
  55. h1l.valign = Gtk.Align.CENTER;
  56. var p1l = new Gtk.Label(null);
  57. p1l.set_markup(p1);
  58. p1l.valign = Gtk.Align.CENTER;
  59. var p2l = new Gtk.Label(null);
  60. p2l.get_style_context().add_class("colorfast-link");
  61. p2l.set_markup(p2);
  62. p2l.valign = Gtk.Align.CENTER;
  63. p2l.activate_link.connect(() => {
  64. try {
  65. string CROWN_DEPLOY_URL = CROWN_LATEST_DOCS_URL + "/deploying";
  66. string CROWN_DEPLOY_CONFIG_URL = CROWN_DEPLOY_URL + "/%s.html".printf(target_platform.to_key());
  67. AppInfo.launch_default_for_uri(CROWN_DEPLOY_CONFIG_URL, null);
  68. } catch (Error e) {
  69. loge(e.message);
  70. }
  71. return true;
  72. });
  73. _check_config_box = new Gtk.Box(Gtk.Orientation.VERTICAL, 12);
  74. _check_config_box.valign = Gtk.Align.CENTER;
  75. _check_config_box.pack_start(h1l);
  76. _check_config_box.pack_start(p1l);
  77. _check_config_box.pack_start(p2l);
  78. _stack.add(_check_config_box);
  79. _stack.add(_deployer_options);
  80. this.pack_start(_stack);
  81. this.map.connect(on_map);
  82. }
  83. public void on_map()
  84. {
  85. if (_check_config != null) {
  86. if (_check_config() != 0)
  87. _stack.set_visible_child(_check_config_box);
  88. else
  89. _stack.set_visible_child(_deployer_options);
  90. } else {
  91. _stack.set_visible_child(_deployer_options);
  92. }
  93. }
  94. }
  95. #if CROWN_PLATFORM_WINDOWS
  96. public bool can_build_32bit_arm = false;
  97. #else
  98. public bool can_build_32bit_arm = true;
  99. #endif
  100. public class DeployDialog : Gtk.Window
  101. {
  102. public RuntimeInstance _editor;
  103. public Project _project;
  104. // Android page.
  105. public Gtk.Button _android_deploy_button;
  106. public InputFile _android_output_path;
  107. public InputEnum _android_config;
  108. public InputBool _android_armv7;
  109. public InputBool _android_armv8;
  110. public InputBool _android_use_debug_keystore;
  111. public InputFile _android_keystore;
  112. public Gtk.Entry _android_keystore_password;
  113. public Gtk.Entry _android_key_alias;
  114. public Gtk.Entry _android_key_password;
  115. public InputString _android_app_title;
  116. public Gtk.Entry _android_app_identifier;
  117. public Gtk.Entry _android_app_version_code;
  118. public Gtk.Entry _android_app_version_name;
  119. public PropertyGridSet _android_set;
  120. public Gtk.Box _android_box;
  121. public AndroidDeployer _android;
  122. public DeployerPage _android_page;
  123. // HTML5 page.
  124. public Gtk.Button _html5_deploy_button;
  125. public InputFile _html5_output_path;
  126. public InputEnum _html5_config;
  127. public InputString _html5_app_title;
  128. public PropertyGridSet _html5_set;
  129. public Gtk.Box _html5_box;
  130. public HTML5Deployer _html5;
  131. public DeployerPage _html5_page;
  132. // Linux page.
  133. public Gtk.Button _linux_deploy_button;
  134. public InputFile _linux_output_path;
  135. public InputEnum _linux_config;
  136. public InputString _linux_app_title;
  137. public PropertyGridSet _linux_set;
  138. public Gtk.Box _linux_box;
  139. public DeployerPage _linux_page;
  140. // Windows page.
  141. public Gtk.Button _windows_deploy_button;
  142. public InputFile _windows_output_path;
  143. public InputEnum _windows_config;
  144. public InputString _windows_app_title;
  145. public PropertyGridSet _windows_set;
  146. public Gtk.Box _windows_box;
  147. public DeployerPage _windows_page;
  148. public Gtk.Notebook _notebook;
  149. public Gtk.EventControllerKey _controller_key;
  150. public DeployDialog(Project project, RuntimeInstance editor)
  151. {
  152. this.title = "Deploy";
  153. this.set_icon_name(CROWN_EDITOR_ICON_NAME);
  154. _project = project;
  155. _editor = editor;
  156. // Android page.
  157. _android_deploy_button = make_deploy_button(TargetPlatform.ANDROID);
  158. _android_deploy_button.clicked.connect(() => {
  159. // Validate input fields.
  160. string? output_path = _android_output_path.value;
  161. if (output_path == null) {
  162. loge("Select a valid output Destination");
  163. return;
  164. }
  165. string app_title = _android_app_title.value.strip();
  166. if (app_title.length == 0) {
  167. loge("Enter a valid App Title");
  168. return;
  169. }
  170. string app_identifier = _android_app_identifier.text.strip();
  171. if (app_title.length == 0 || app_identifier.split(".").length != 3) {
  172. loge("Enter a valid App Identifier");
  173. return;
  174. }
  175. int app_version_code;
  176. if (int.try_parse(_android_app_version_code.text, out app_version_code) == false) {
  177. loge("Enter a valid App Version Code");
  178. return;
  179. }
  180. string app_version_name = _android_app_version_name.text.strip();
  181. if (app_version_name.length == 0) {
  182. loge("Enter a valid App Version Name");
  183. return;
  184. }
  185. string? keystore_path = _android_use_debug_keystore.value
  186. ? GLib.Path.build_filename(GLib.Environment.get_home_dir(), ".android", "debug.keystore")
  187. : _android_keystore.value
  188. ;
  189. if (keystore_path == null) {
  190. loge("Enter a valid Keystore file");
  191. return;
  192. }
  193. string keystore_pass = _android_use_debug_keystore.value
  194. ? "android"
  195. : _android_keystore_password.text
  196. ;
  197. if (keystore_path.length == 0) {
  198. loge("Enter a valid Keystore Password");
  199. return;
  200. }
  201. string key_alias = _android_use_debug_keystore.value
  202. ? "androiddebugkey"
  203. : _android_key_alias.text
  204. ;
  205. if (key_alias.length == 0) {
  206. loge("Enter a valid Key Alias");
  207. return;
  208. }
  209. string key_pass = _android_use_debug_keystore.value
  210. ? "android"
  211. : _android_key_password.text
  212. ;
  213. if (key_pass.length == 0) {
  214. loge("Enter a valid Key Password");
  215. return;
  216. }
  217. TargetArch[] archs =
  218. {
  219. TargetArch.ARM,
  220. TargetArch.ARM64
  221. };
  222. for (int ii = 0; ii < archs.length; ++ii) {
  223. if (archs[ii] == TargetArch.ARM && !_android_armv7.value)
  224. continue;
  225. if (archs[ii] == TargetArch.ARM64 && !_android_armv8.value)
  226. continue;
  227. // Create the package.
  228. GLib.Variant paramz[] =
  229. {
  230. (string)output_path,
  231. int.parse(_android_config.value),
  232. app_title,
  233. app_identifier,
  234. app_version_code,
  235. app_version_name,
  236. keystore_path,
  237. keystore_pass,
  238. key_alias,
  239. key_pass,
  240. archs[ii]
  241. };
  242. GLib.Application.get_default().activate_action("create-package-android"
  243. , new GLib.Variant.tuple(paramz));
  244. }
  245. });
  246. _android_output_path = new InputFile(Gtk.FileChooserAction.SELECT_FOLDER);
  247. _android_config = make_deploy_config_combo();
  248. _android_armv7 = new InputBool();
  249. _android_armv7.value = false;
  250. _android_armv8 = new InputBool();
  251. _android_armv8.value = true;
  252. _android_use_debug_keystore = new InputBool();
  253. _android_use_debug_keystore.value_changed.connect(() => { android_set_debug_keystore(); });
  254. _android_use_debug_keystore.value = true;
  255. _android_keystore = new InputFile(Gtk.FileChooserAction.OPEN);
  256. _android_keystore_password = new Gtk.Entry();
  257. _android_keystore_password.set_visibility(false);
  258. _android_keystore_password.input_purpose = Gtk.InputPurpose.PASSWORD;
  259. _android_key_alias = new Gtk.Entry();
  260. _android_key_password = new Gtk.Entry();
  261. _android_key_password.set_visibility(false);
  262. _android_key_password.input_purpose = Gtk.InputPurpose.PASSWORD;
  263. _android_app_title = new InputString();
  264. _android_app_title._entry.placeholder_text = "My Application";
  265. _android_app_title.value = _project.name();
  266. _android_app_identifier = new Gtk.Entry();
  267. _android_app_identifier.placeholder_text = "org.company.product";
  268. _android_app_version_code = new Gtk.Entry();
  269. _android_app_version_code.input_purpose = Gtk.InputPurpose.DIGITS;
  270. _android_app_version_code.placeholder_text = "1";
  271. _android_app_version_name = new Gtk.Entry();
  272. _android_app_version_name.placeholder_text = "1.0";
  273. android_set_debug_keystore();
  274. _android_set = new PropertyGridSet();
  275. // Android Output grid.
  276. PropertyGrid cv;
  277. cv = new PropertyGrid();
  278. cv.column_homogeneous = true;
  279. cv.add_row("Destination", _android_output_path);
  280. cv.add_row("Config", _android_config);
  281. cv.add_row("ARMv7-A", _android_armv7).sensitive = can_build_32bit_arm;
  282. _android_armv7.sensitive = can_build_32bit_arm;
  283. cv.add_row("ARMv8-A", _android_armv8);
  284. _android_set.add_property_grid(cv, "Output");
  285. // Android Application.
  286. cv = new PropertyGrid();
  287. cv.column_homogeneous = true;
  288. cv.add_row("Title", _android_app_title);
  289. cv.add_row("Identifier", _android_app_identifier);
  290. cv.add_row("Version Code", _android_app_version_code);
  291. cv.add_row("Version Name", _android_app_version_name);
  292. _android_set.add_property_grid(cv, "Application");
  293. // Android Signing.
  294. cv = new PropertyGrid();
  295. cv.column_homogeneous = true;
  296. cv.add_row("Use debug keystore", _android_use_debug_keystore);
  297. cv.add_row("Keystore", _android_keystore);
  298. cv.add_row("Keystore password", _android_keystore_password);
  299. cv.add_row("Alias", _android_key_alias);
  300. cv.add_row("Key password", _android_key_password);
  301. _android_set.add_property_grid(cv, "Signing");
  302. // Android box.
  303. _android_box = new Gtk.Box(Gtk.Orientation.VERTICAL, 0);
  304. _android_box.pack_start(_android_deploy_button, false, true, 0);
  305. _android_box.pack_start(_android_set, false, true, 0);
  306. _android = new AndroidDeployer();
  307. _android_page = new DeployerPage(TargetPlatform.ANDROID, _android_box, _android.check_config);
  308. // HTML5 page.
  309. _html5_deploy_button = make_deploy_button(TargetPlatform.HTML5);
  310. _html5_deploy_button.clicked.connect(() => {
  311. // Validate input fields.
  312. string? output_path = _html5_output_path.value;
  313. if (output_path == null) {
  314. loge("Select a valid output Destination");
  315. return;
  316. }
  317. string app_title = _html5_app_title.value.strip();
  318. if (app_title.length == 0) {
  319. loge("Enter a valid Title");
  320. return;
  321. }
  322. // Create the package.
  323. GLib.Variant paramz[] =
  324. {
  325. (string)output_path,
  326. int.parse(_html5_config.value),
  327. app_title
  328. };
  329. GLib.Application.get_default().activate_action("create-package-html5"
  330. , new GLib.Variant.tuple(paramz));
  331. });
  332. _html5_output_path = new InputFile(Gtk.FileChooserAction.SELECT_FOLDER);
  333. _html5_config = make_deploy_config_combo();
  334. _html5_app_title = new InputString();
  335. _html5_app_title._entry.placeholder_text = "My Application";
  336. _html5_app_title.value = _project.name();
  337. _html5_set = new PropertyGridSet();
  338. // HTML5 box.
  339. _html5_box = new Gtk.Box(Gtk.Orientation.VERTICAL, 0);
  340. _html5_box.pack_start(_html5_deploy_button, false, true, 0);
  341. _html5_box.pack_start(_html5_set, false, true, 0);
  342. _html5 = new HTML5Deployer();
  343. _html5_page = new DeployerPage(TargetPlatform.HTML5, _html5_box, _html5.check_config);
  344. // HTML5 General page.
  345. cv = new PropertyGrid();
  346. cv.column_homogeneous = true;
  347. cv.add_row("Destination", _html5_output_path);
  348. cv.add_row("Config", _html5_config);
  349. _html5_set.add_property_grid(cv, "Output");
  350. // HTML5 Application.
  351. cv = new PropertyGrid();
  352. cv.column_homogeneous = true;
  353. cv.add_row("Title", _html5_app_title);
  354. _html5_set.add_property_grid(cv, "Application");
  355. // Linux page.
  356. _linux_deploy_button = make_deploy_button(TargetPlatform.LINUX);
  357. _linux_deploy_button.clicked.connect(() => {
  358. // Validate input fields.
  359. string? output_path = _linux_output_path.value;
  360. if (output_path == null) {
  361. loge("Select a valid output Destination");
  362. return;
  363. }
  364. string app_title = _linux_app_title.value.strip();
  365. if (app_title.length == 0) {
  366. loge("Enter a valid Title");
  367. return;
  368. }
  369. // Create the package.
  370. GLib.Variant paramz[] =
  371. {
  372. (string)output_path,
  373. int.parse(_linux_config.value),
  374. app_title
  375. };
  376. GLib.Application.get_default().activate_action("create-package-linux"
  377. , new GLib.Variant.tuple(paramz));
  378. });
  379. _linux_output_path = new InputFile(Gtk.FileChooserAction.SELECT_FOLDER);
  380. _linux_config = make_deploy_config_combo();
  381. _linux_app_title = new InputString();
  382. _linux_app_title._entry.placeholder_text = "My Application";
  383. _linux_app_title.value = _project.name();
  384. _linux_set = new PropertyGridSet();
  385. // Linux box.
  386. _linux_box = new Gtk.Box(Gtk.Orientation.VERTICAL, 0);
  387. _linux_box.pack_start(_linux_deploy_button, false, true, 0);
  388. _linux_box.pack_start(_linux_set, false, true, 0);
  389. _linux_page = new DeployerPage(TargetPlatform.LINUX, _linux_box);
  390. // Linux General page.
  391. cv = new PropertyGrid();
  392. cv.column_homogeneous = true;
  393. cv.add_row("Destination", _linux_output_path);
  394. cv.add_row("Config", _linux_config);
  395. _linux_set.add_property_grid(cv, "Output");
  396. // Linux Application.
  397. cv = new PropertyGrid();
  398. cv.column_homogeneous = true;
  399. cv.add_row("Title", _linux_app_title);
  400. _linux_set.add_property_grid(cv, "Application");
  401. // Windows page.
  402. _windows_deploy_button = make_deploy_button(TargetPlatform.WINDOWS);
  403. _windows_deploy_button.clicked.connect(() => {
  404. // Validate input fields.
  405. string? output_path = _windows_output_path.value;
  406. if (output_path == null) {
  407. loge("Select a valid output Destination");
  408. return;
  409. }
  410. string app_title = _windows_app_title.value.strip();
  411. if (app_title.length == 0) {
  412. loge("Enter a valid Title");
  413. return;
  414. }
  415. // Create the package.
  416. GLib.Variant paramz[] =
  417. {
  418. (string)output_path,
  419. int.parse(_windows_config.value),
  420. app_title
  421. };
  422. GLib.Application.get_default().activate_action("create-package-windows"
  423. , new GLib.Variant.tuple(paramz));
  424. });
  425. _windows_output_path = new InputFile(Gtk.FileChooserAction.SELECT_FOLDER);
  426. _windows_config = make_deploy_config_combo();
  427. _windows_app_title = new InputString();
  428. _windows_app_title._entry.placeholder_text = "My Application";
  429. _windows_app_title.value = _project.name();
  430. _windows_set = new PropertyGridSet();
  431. // Windows box.
  432. _windows_box = new Gtk.Box(Gtk.Orientation.VERTICAL, 0);
  433. _windows_box.pack_start(_windows_deploy_button, false, true, 0);
  434. _windows_box.pack_start(_windows_set, false, true, 0);
  435. _windows_page = new DeployerPage(TargetPlatform.LINUX, _windows_box);
  436. // Windows General page.
  437. cv = new PropertyGrid();
  438. cv.column_homogeneous = true;
  439. cv.add_row("Destination", _windows_output_path);
  440. cv.add_row("Config", _windows_config);
  441. _windows_set.add_property_grid(cv, "Output");
  442. // Windows Application.
  443. cv = new PropertyGrid();
  444. cv.column_homogeneous = true;
  445. cv.add_row("Title", _windows_app_title);
  446. _windows_set.add_property_grid(cv, "Application");
  447. // Add pages.
  448. _notebook = new Gtk.Notebook();
  449. #if CROWN_PLATFORM_LINUX
  450. _notebook.append_page(_linux_page, new Gtk.Label(TargetPlatform.LINUX.to_label()));
  451. #elif CROWN_PLATFORM_WINDOWS
  452. _notebook.append_page(_windows_page, new Gtk.Label(TargetPlatform.WINDOWS.to_label()));
  453. #endif
  454. _notebook.append_page(_android_page, new Gtk.Label(TargetPlatform.ANDROID.to_label()));
  455. _notebook.append_page(_html5_page, new Gtk.Label(TargetPlatform.HTML5.to_label()));
  456. _notebook.vexpand = true;
  457. _notebook.show_border = false;
  458. _controller_key = new Gtk.EventControllerKey(this);
  459. _controller_key.key_pressed.connect(on_key_pressed);
  460. this.add(_notebook);
  461. }
  462. public bool on_key_pressed(uint keyval, uint keycode, Gdk.ModifierType state)
  463. {
  464. if (keyval == Gdk.Key.Escape)
  465. this.close();
  466. return Gdk.EVENT_PROPAGATE;
  467. }
  468. public void android_set_debug_keystore()
  469. {
  470. bool sensitive = !_android_use_debug_keystore.value;
  471. _android_keystore.sensitive = sensitive;
  472. _android_keystore_password.sensitive = sensitive;
  473. _android_key_alias.sensitive = sensitive;
  474. _android_key_password.sensitive = sensitive;
  475. }
  476. }
  477. } /* namespace Crown */