deploy_dialog.vala 16 KB

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