diff --git a/data/icons/128/com.github.writeas.writeas-gtk.png b/data/icons/128/writeas-gtk.png similarity index 100% rename from data/icons/128/com.github.writeas.writeas-gtk.png rename to data/icons/128/writeas-gtk.png diff --git a/data/icons/16/com.github.writeas.writeas-gtk.png b/data/icons/16/writeas-gtk.png similarity index 100% rename from data/icons/16/com.github.writeas.writeas-gtk.png rename to data/icons/16/writeas-gtk.png diff --git a/data/icons/24/com.github.writeas.writeas-gtk.png b/data/icons/24/writeas-gtk.png similarity index 100% rename from data/icons/24/com.github.writeas.writeas-gtk.png rename to data/icons/24/writeas-gtk.png diff --git a/data/icons/32/com.github.writeas.writeas-gtk.png b/data/icons/32/writeas-gtk.png similarity index 100% rename from data/icons/32/com.github.writeas.writeas-gtk.png rename to data/icons/32/writeas-gtk.png diff --git a/data/icons/48/com.github.writeas.writeas-gtk.png b/data/icons/48/writeas-gtk.png similarity index 100% rename from data/icons/48/com.github.writeas.writeas-gtk.png rename to data/icons/48/writeas-gtk.png diff --git a/data/icons/64/com.github.writeas.writeas-gtk.png b/data/icons/64/writeas-gtk.png similarity index 100% rename from data/icons/64/com.github.writeas.writeas-gtk.png rename to data/icons/64/writeas-gtk.png diff --git a/data/meson.build b/data/meson.build index 1c92abd..73b7444 100644 --- a/data/meson.build +++ b/data/meson.build @@ -1,14 +1,24 @@ icon_sizes = ['16', '24', '32', '48', '64', '128'] foreach i : icon_sizes install_data( join_paths('icons', i, meson.project_name() + '.png'), - install_dir: join_paths(get_option('datadir'), 'icons', 'hicolor', i + 'x' + i, 'apps' - ) + install_dir: join_paths(get_option('datadir'), 'icons', 'hicolor', i + 'x' + i, 'apps'), + rename: '@0@.png'.format(app_id) ) endforeach -install_data('com.github.writeas.writeas-gtk.desktop', - install_dir: join_paths(get_option('datadir'), 'applications')) -install_data('com.github.writeas.writeas-gtk.appdata.xml', - install_dir: join_paths(get_option('datadir'), 'metainfo')) +data_conf = configuration_data() +data_conf.set('app_id', app_id) +configure_file( + input: 'writeas-gtk.desktop.in', + output: '@0@.desktop'.format(app_id), + configuration: data_conf, + install_dir: join_paths(get_option('datadir'), 'applications') +) +configure_file( + input: 'writeas-gtk.appdata.xml.in', + output: '@0@.appdata.xml'.format(app_id), + configuration: data_conf, + install_dir: join_paths(get_option('datadir'), 'metainfo') +) diff --git a/data/com.github.writeas.writeas-gtk.appdata.xml b/data/writeas-gtk.appdata.xml.in similarity index 98% rename from data/com.github.writeas.writeas-gtk.appdata.xml rename to data/writeas-gtk.appdata.xml.in index bbec7a3..7107478 100644 --- a/data/com.github.writeas.writeas-gtk.appdata.xml +++ b/data/writeas-gtk.appdata.xml.in @@ -1,114 +1,114 @@ - com.github.writeas.writeas-gtk + @app_id@ GPL-3.0+ CC0 Write.as Publish a thought in seconds

Write.as is a simple writing tool and publishing platform. There's no sign up — just open the app, write something, and publish.

Published posts get a secret, unique link on Write.as that you can share with anyone, or keep to yourself. In either case, you remain private because we don't collect personal information about you.

Write.as https://write.as/ https://write.as/contact https://github.com/writeas/writeas-gtk/issues hello@write.as - com.github.writeas.writeas-gtk + @app_id@ The Write.as editor. https://write.as/img/screens/gtk/serif.png The Write.as editor in dark mode. https://write.as/img/screens/gtk/serif-dark.png https://write.as/img/screens/gtk/sans.png https://write.as/img/screens/gtk/monospace.png none none none none none none none none none none none none none none none none none none none none none moderate none none none none none

This update fixes a few minor visual issues.

  • Fix black bar that appears in the editor on elementary OS
  • Fix currently-selected font not reflected in menu when app first loads

GTK updates and fixes.

  • Fix fonts, padding, cursor color
  • Increase the default font size

Initial release

  • Auto-saving single draft
  • Dark mode on platforms that support it
  • Choose between three fonts
  • Save draft as another file
  • Publish anonymously to Write.as
25
diff --git a/data/com.github.writeas.writeas-gtk.desktop b/data/writeas-gtk.desktop.in similarity index 73% rename from data/com.github.writeas.writeas-gtk.desktop rename to data/writeas-gtk.desktop.in index 1c4f71a..0ec22b5 100644 --- a/data/com.github.writeas.writeas-gtk.desktop +++ b/data/writeas-gtk.desktop.in @@ -1,12 +1,12 @@ [Desktop Entry] Type=Application Name=Write.as Comment=Publish a thought in seconds. -Exec=com.github.writeas.writeas-gtk -Icon=com.github.writeas.writeas-gtk +Exec=@app_id@ +Icon=@app_id@ Terminal=false MimeType= Categories=GTK;Office;Publishing; Keywords=blog;text;editor;publish; StartupNotify=true diff --git a/debian/rules b/debian/rules index 022735d..d3fbd4c 100755 --- a/debian/rules +++ b/debian/rules @@ -1,33 +1,33 @@ #!/usr/bin/make -f # -*- makefile -*- # Sample debian/rules that uses debhelper. # This file was originally written by Joey Hess and Craig Small. # As a special exception, when this file is copied by dh-make into a # dh-make output file, you may use that output file without restriction. # This special exception was added by Craig Small in version 0.37 of dh-make. # This file was extended to incorporate a Meson/Ninja build system. # Uncomment this to turn on verbose mode. #export DH_VERBOSE=1 %: dh $@ override_dh_auto_clean: rm -rf debian/build override_dh_auto_configure: mkdir -p debian/build - cd debian/build && meson --prefix=/usr ../.. + cd debian/build && meson --prefix=/usr ../.. -Dplatform=elementary override_dh_auto_build: cd debian/build && ninja -v && ninja build override_dh_auto_test: cd debian/build && ninja test override_dh_auto_install: cd debian/build && DESTDIR=${CURDIR}/debian/com.github.writeas.writeas-gtk ninja install mkdir -p debian/com.github.writeas.writeas-gtk/usr/bin cp bin/writeas debian/com.github.writeas.writeas-gtk/usr/bin/ diff --git a/meson.build b/meson.build index 97e7958..55fbd1a 100644 --- a/meson.build +++ b/meson.build @@ -1,19 +1,27 @@ -project('com.github.writeas.writeas-gtk', ['vala', 'c'], +project('writeas-gtk', ['vala', 'c'], version: '1.0.2', license: 'GPL', - meson_version: '>=0.40.1') + meson_version: '>=0.46.0') i18n = import('i18n') + +if get_option('platform') == 'elementary' + app_id = 'com.github.writeas.writeas-gtk' +else + app_id = 'writeas-gtk' +endif + conf = configuration_data() -conf.set_quoted('GETTEXT_PACKAGE', meson.project_name()) -configure_file(output: 'config.h', configuration: conf) +conf.set_quoted('GETTEXT_PACKAGE', app_id) +conf.set_quoted('APP_ID', app_id) +config_h = configure_file(output: 'config.h', configuration: conf) config_h_dir = include_directories('.') run_target('build', command: 'meson/build-cli.sh') subdir('data') subdir('src') subdir('fonts/lora') meson.add_install_script('meson/post_install.py') diff --git a/meson_options.txt b/meson_options.txt new file mode 100644 index 0000000..29bd56e --- /dev/null +++ b/meson_options.txt @@ -0,0 +1 @@ +option('platform', type: 'combo', choices: ['default', 'elementary'], value: 'default') diff --git a/src/application.vala b/src/application.vala index b5968ea..982e530 100644 --- a/src/application.vala +++ b/src/application.vala @@ -1,40 +1,43 @@ /* Copyright © 2018 Write.as This file is part of the Write.as GTK desktop app. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ + +extern const string APP_ID; + public class WriteAs.Application : Gtk.Application { construct { this.flags |= ApplicationFlags.HANDLES_OPEN; Intl.setlocale(LocaleCategory.ALL, ""); Intl.textdomain("write.as"); - application_id = "com.github.writeas.writeas-gtk.desktop"; + application_id = APP_ID + ".desktop"; } public override void activate() { if (get_windows().length() == 0) new WriteAs.MainWindow(this).show_all(); } public override void open(File[] files, string hint) { activate(); // ensure we have a window open. } public static int main(string[] args) { return new WriteAs.Application().run(args); } } diff --git a/src/meson.build b/src/meson.build index 761c5c9..75de727 100644 --- a/src/meson.build +++ b/src/meson.build @@ -1,11 +1,11 @@ -executable('com.github.writeas.writeas-gtk', +executable(app_id, 'application.vala', 'window.vala', 'Granite/Accels.vala', 'Granite/ModeSwitch.vala', c_args: ['-include', 'config.h'], link_args: '-lm', dependencies: [dependency('gtk+-3.0'), dependency('gtksourceview-3.0')], install: true ) diff --git a/src/window.vala b/src/window.vala index 8df938f..a353edf 100644 --- a/src/window.vala +++ b/src/window.vala @@ -1,414 +1,414 @@ /* Copyright © 2018 Write.as This file is part of the Write.as GTK desktop app. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ public class WriteAs.MainWindow : Gtk.ApplicationWindow { private Gtk.TextView canvas; private Gtk.HeaderBar header; private Granite.ModeSwitch darkmode_switch; private Gtk.RadioMenuItem font_serif_option; private Gtk.RadioMenuItem font_sans_option; private Gtk.RadioMenuItem font_wrap_option; private static string data_dir = ".writeas"; private static string version = "1.0.2"; private int font_size = 16; private bool dark_mode = false; private string font = "Lora, 'Palatino Linotype'," + "'Book Antiqua', 'New York', 'DejaVu serif', serif"; private string fontstyle = "serif"; private bool text_changed = false; private bool is_initializing = true; construct { header = new Gtk.HeaderBar(); header.title = "Write.as"; construct_toolbar(); build_keyboard_shortcuts(); var scrolled = new Gtk.ScrolledWindow(null, null); canvas = new Gtk.SourceView(); canvas.wrap_mode = Gtk.WrapMode.WORD_CHAR; scrolled.add(canvas); add(scrolled); size_allocate.connect((_) => {adjust_text_style();}); canvas.event_after.connect((evt) => { // TODO This word count algorithm may be quite naive // and could do improvement. var word_count = canvas.buffer.text.split(" ").length; header.subtitle = ngettext("%i word","%i words",word_count).printf(word_count); text_changed = true; }); Timeout.add_full(Priority.DEFAULT_IDLE, 100/*ms*/, () => { if (!text_changed) return Source.CONTINUE; var text = canvas.buffer.text; // This happens sometimes for some reason, but it's difficult to debug. if (text == "") return Source.CONTINUE; try { draft_file().replace_contents(text.data, null, false, FileCreateFlags.PRIVATE | FileCreateFlags.REPLACE_DESTINATION, null); text_changed = false; } catch (Error err) {/* We'll try again anyways. */} return Source.CONTINUE; }); adjust_text_style(false); } public MainWindow(Gtk.Application app) { stdout.printf("writeas-gtk v%s\n", version); set_application(app); - icon_name = "com.github.writeas.writeas-gtk"; + icon_name = APP_ID; init_folder(); try { open_file(draft_file()); } catch (Error err) {} restore_styles(); set_default_size(800, 600); is_initializing = false; } private static void init_folder() { var home = File.new_for_path(get_data_dir()); try { home.make_directory(); } catch (Error e) { stderr.printf("Create data dir: %s\n", e.message); } } private static string get_data_dir() { return Environment.get_home_dir() + "/" + data_dir; } private static File draft_file() { var home = File.new_for_path(get_data_dir()); return home.get_child("draft.txt"); } private static bool supports_dark_theme() { var theme = Gtk.Settings.get_default().gtk_theme_name; foreach (var datapath in Environment.get_system_data_dirs()) { var path = File.new_for_path(Path.build_filename(datapath, "themes", theme)); if (path.get_child("gtk-dark.css").query_exists()) return true; try { var enumerator = path.enumerate_children("standard::*", 0); FileInfo info = null; while ((info = enumerator.next_file()) != null) { var fullpath = path.get_child(info.get_name()).get_child("gtk-dark.css"); if (fullpath.query_exists()) return true; } } catch (Error err) {/* Might be missing something, but no biggy. */} } return false; } private void construct_toolbar() { header.show_close_button = true; set_titlebar(header); var publish_button = new Gtk.Button.from_icon_name("document-send", Gtk.IconSize.LARGE_TOOLBAR); publish_button.tooltip_markup = Granite.markup_accel_tooltip ( {"Return"}, _("Publish to Write.as on the web") ); publish_button.clicked.connect(() => { canvas.buffer.text += "\n\n" + publish(); canvas.grab_focus(); }); header.pack_end(publish_button); darkmode_switch = new Granite.ModeSwitch.from_icon_name ("display-brightness-symbolic", "weather-clear-night-symbolic"); darkmode_switch.primary_icon_tooltip_text = ("Light theme"); darkmode_switch.secondary_icon_tooltip_text = ("Dark theme"); darkmode_switch.tooltip_markup = Granite.markup_accel_tooltip ( {"T"}, _("Toggle light/dark theme") ); darkmode_switch.valign = Gtk.Align.CENTER; var settings = Gtk.Settings.get_default(); darkmode_switch.notify["active"].connect(() => { settings.gtk_application_prefer_dark_theme = darkmode_switch.active; dark_mode = darkmode_switch.active; if (!is_initializing) theme_save(); canvas.grab_focus(); }); if (supports_dark_theme()) header.pack_end(darkmode_switch); var fonts = new Gtk.MenuButton(); fonts.tooltip_text = _("Change document font"); fonts.image = new Gtk.Image.from_icon_name("font-x-generic", Gtk.IconSize.LARGE_TOOLBAR); fonts.popup = new Gtk.Menu(); header.pack_start(fonts); font_serif_option = build_fontoption(fonts.popup, _("Serif"), "serif", font); font_sans_option = build_fontoption(fonts.popup, _("Sans-serif"), "sans", "'Open Sans', 'Segoe UI', Tahoma, Arial, sans-serif"); font_wrap_option = build_fontoption(fonts.popup, _("Monospace"), "wrap", "Hack, consolas," + "Menlo-Regular, Menlo, Monaco, 'ubuntu mono', monospace"); fonts.popup.show_all(); } private unowned SList? font_options = null; private Gtk.RadioMenuItem build_fontoption(Gtk.Menu menu, string label, string fontstyle, string families) { var option = new Gtk.RadioMenuItem.with_label(font_options, label); font_options = option.get_group(); option.activate.connect(() => { this.font = families; this.fontstyle = fontstyle; adjust_text_style(!is_initializing); canvas.grab_focus(); }); var styles = option.get_style_context(); var provider = new Gtk.CssProvider(); try { provider.load_from_data("* {font-family: %s;}".printf(families)); styles.add_provider(provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION); } catch (Error e) { warning(e.message); } menu.add(option); return option; } public override void grab_focus() { canvas.grab_focus(); } private KeyFile theme = new KeyFile(); private void restore_styles() { try { loaded_theme = true; theme.load_from_file(get_data_dir() + "/prefs.ini", KeyFileFlags.NONE); dark_mode = theme.get_boolean("Theme", "darkmode"); darkmode_switch.active = dark_mode; Gtk.Settings.get_default().gtk_application_prefer_dark_theme = dark_mode; font_size = theme.get_integer("Theme", "fontsize"); font = theme.get_string("Post", "font"); fontstyle = theme.get_string("Post", "fontstyle"); // Select the current font in the menu if (fontstyle == "serif") { font_serif_option.set_active(true); } else if (fontstyle == "sans") { font_sans_option.set_active(true); } else if (fontstyle == "wrap") { font_wrap_option.set_active(true); } adjust_text_style(false); } catch (Error err) {/* No biggy... */} } private Gtk.CssProvider cur_styles = null; // So the theme isn't read before it's saved. private bool loaded_theme = false; private void adjust_text_style(bool save_theme = true) { try { if (cur_styles != null) Gtk.StyleContext.remove_provider_for_screen(Gdk.Screen.get_default(), cur_styles); var padding = canvas.get_allocated_width()*0.10; var css = ("textview {font-family: %s; font-size: %dpx; padding: 20px 0;" + " caret-color: #5ac4ee;}").printf(font, font_size); cur_styles = new Gtk.CssProvider(); cur_styles.load_from_data(css); Gtk.StyleContext.add_provider_for_screen(Gdk.Screen.get_default(), cur_styles, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION); canvas.left_margin = canvas.right_margin = (int) padding; if (save_theme) theme_save(); } catch (Error e) { warning(e.message); } } private void theme_save() { if (!loaded_theme) return; theme.set_boolean("Theme", "darkmode", dark_mode); theme.set_integer("Theme", "fontsize", font_size); theme.set_string("Post", "font", font); theme.set_string("Post", "fontstyle", fontstyle); try { theme.save_to_file(get_data_dir() + "/prefs.ini"); } catch (FileError err) {/* Oh well. */} } private string publish() { try { if (text_changed) {; draft_file().replace_contents(canvas.buffer.text.data, null, false, FileCreateFlags.PRIVATE | FileCreateFlags.REPLACE_DESTINATION, null); text_changed = false; } var cmd = "sh -c 'cat ~/" + data_dir + "/draft.txt | writeas --md --font %s --user-agent \"writeas-gtk v" + version + "\"'"; cmd = cmd.printf(fontstyle); string stdout, stderr; int status; Process.spawn_command_line_sync(cmd, out stdout, out stderr, out status); // Open it in the browser if (status == 0) { var browser = AppInfo.get_default_for_uri_scheme("https"); var urls = new List(); urls.append(stdout.strip()); browser.launch_uris(urls, null); } return stderr.strip(); } catch (Error err) { return err.message; } } /* --- */ private void build_keyboard_shortcuts() { /* These operations are not exposed to the UI as buttons, as most people are very familiar with them and they are not the focus of this app. */ var accels = new Gtk.AccelGroup(); // App operations accels.connect(Gdk.Key.W, Gdk.ModifierType.CONTROL_MASK, Gtk.AccelFlags.VISIBLE | Gtk.AccelFlags.LOCKED, (g,a,k,m) => quit()); accels.connect(Gdk.Key.Q, Gdk.ModifierType.CONTROL_MASK, Gtk.AccelFlags.VISIBLE | Gtk.AccelFlags.LOCKED, (g,a,k,m) => quit()); // File operations accels.connect(Gdk.Key.S, Gdk.ModifierType.CONTROL_MASK, Gtk.AccelFlags.VISIBLE | Gtk.AccelFlags.LOCKED, (g,a,k,m) => save_as()); accels.connect(Gdk.Key.S, Gdk.ModifierType.CONTROL_MASK | Gdk.ModifierType.SHIFT_MASK, Gtk.AccelFlags.VISIBLE | Gtk.AccelFlags.LOCKED, (g,a,k,m) => save_as()); // Adjust text size accels.connect(Gdk.Key.minus, Gdk.ModifierType.CONTROL_MASK, Gtk.AccelFlags.VISIBLE | Gtk.AccelFlags.LOCKED, (g,a,k,m) => { if (font_size < 3) { return false; } if (font_size <= 10) { font_size -= 1; } else { font_size -= 2; } adjust_text_style(true); return true; }); accels.connect(Gdk.Key.equal, Gdk.ModifierType.CONTROL_MASK, Gtk.AccelFlags.VISIBLE | Gtk.AccelFlags.LOCKED, (g,a,k,m) => { if (font_size < 10) { font_size += 1; } else { font_size += 2; } adjust_text_style(true); return true; }); // Toggle theme with Ctrl+T accels.connect(Gdk.Key.T, Gdk.ModifierType.CONTROL_MASK, Gtk.AccelFlags.VISIBLE | Gtk.AccelFlags.LOCKED, (g,a,k,m) => { darkmode_switch.active = !darkmode_switch.active; return true; }); // Publish with Ctrl+Enter accels.connect(Gdk.Key.Return, Gdk.ModifierType.CONTROL_MASK, Gtk.AccelFlags.VISIBLE | Gtk.AccelFlags.LOCKED, (g,a,k,m) => { canvas.buffer.text += "\n\n" + publish(); return true; }); add_accel_group(accels); } private bool save_as() { try { var file = prompt_file(Gtk.FileChooserAction.SAVE, _("Save as")); file.replace_contents(canvas.buffer.text.data, null, false, FileCreateFlags.PRIVATE | FileCreateFlags.REPLACE_DESTINATION, null); } catch (Error e) { // It's fine... } return true; } private File prompt_file(Gtk.FileChooserAction mode, string action) throws UserCancellable { var file_chooser = new Gtk.FileChooserDialog(action, this, mode, _("Cancel"), Gtk.ResponseType.CANCEL, action, Gtk.ResponseType.ACCEPT); file_chooser.select_multiple = false; var filter = new Gtk.FileFilter(); filter.add_mime_type("text/plain"); file_chooser.set_filter(filter); var resp = file_chooser.run(); file_chooser.close(); if (resp == Gtk.ResponseType.ACCEPT) { return file_chooser.get_file(); } else { throw new UserCancellable.USER_CANCELLED("FileChooserDialog"); } } public void open_file(File file) throws Error { uint8[] text; file.load_contents(null, out text, null); canvas.buffer.text = (string) text; } private bool quit() { this.close(); return true; } } errordomain WriteAs.UserCancellable {USER_CANCELLED}