diff --git a/data/meson.build b/data/meson.build index b5eead1..413e7ee 100644 --- a/data/meson.build +++ b/data/meson.build @@ -1,26 +1,25 @@ 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'), - rename: '@0@.png'.format(app_id) ) endforeach i18n.merge_file( input: meson.project_name() + '.desktop.in', output: meson.project_name() + '.desktop', po_dir: join_paths(meson.source_root(), 'po', 'extra'), type: 'desktop', install: true, install_dir: join_paths(get_option('datadir'), 'applications') ) i18n.merge_file( input: meson.project_name() + '.appdata.xml.in', output: meson.project_name() + '.appdata.xml', po_dir: join_paths(meson.source_root(), 'po', 'extra'), install: true, install_dir: join_paths(get_option('datadir'), 'metainfo') ) diff --git a/data/writeas-gtk.appdata.xml.in b/data/writeas-gtk.appdata.xml.in index 7107478..bbec7a3 100644 --- a/data/writeas-gtk.appdata.xml.in +++ b/data/writeas-gtk.appdata.xml.in @@ -1,114 +1,114 @@ - @app_id@ + com.github.writeas.writeas-gtk 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 - @app_id@ + com.github.writeas.writeas-gtk 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/writeas-gtk.desktop.in b/data/writeas-gtk.desktop.in index 0ec22b5..1c4f71a 100644 --- a/data/writeas-gtk.desktop.in +++ b/data/writeas-gtk.desktop.in @@ -1,12 +1,12 @@ [Desktop Entry] Type=Application Name=Write.as Comment=Publish a thought in seconds. -Exec=@app_id@ -Icon=@app_id@ +Exec=com.github.writeas.writeas-gtk +Icon=com.github.writeas.writeas-gtk Terminal=false MimeType= Categories=GTK;Office;Publishing; Keywords=blog;text;editor;publish; StartupNotify=true diff --git a/meson.build b/meson.build index 041bf86..1782140 100644 --- a/meson.build +++ b/meson.build @@ -1,32 +1,35 @@ -project('com.github.writeas.writeas-gtk', 'vala', 'c', +project( + 'com.github.writeas.writeas-gtk', + 'vala', 'c', version: '1.0.2', - license: 'GPL', + license: 'GPL-3.0', ) i18n = import('i18n') -build_platform = get_option('platform') -if build_platform == 'elementary' - app_id = 'com.github.writeas.writeas-gtk' -else - app_id = 'writeas-gtk' -endif +add_project_arguments( + '-DGETTEXT_PACKAGE="@0@"'.format(meson.project_name()), + language: 'c' +) -conf = configuration_data() -conf.set_quoted('GETTEXT_PACKAGE', app_id) -conf.set_quoted('APP_ID', app_id) -conf.set_quoted('BUILD_PLATFORM', build_platform) -config_h = configure_file(output: 'config.h', configuration: conf) -config_h_dir = include_directories('.') +dependencies = [ + dependency('gtk+-3.0'), + dependency('gtksourceview-3.0') +] -add_global_arguments('-DGETTEXT_PACKAGE="@0@"'.format (meson.project_name()), language:'c') +subdir('src') -run_target('build', command: 'meson/build-cli.sh') +executable( + meson.project_name(), + sources, + dependencies: dependencies, + install: true +) subdir('data') subdir('po') -subdir('src') + subdir('fonts/lora') meson.add_install_script('meson/post_install.py') diff --git a/meson/build-cli.sh b/meson/build-cli.sh deleted file mode 100755 index 023c655..0000000 --- a/meson/build-cli.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/bash - -exec_name=writeas - -echo "Building $exec_name CLI..." -gb build github.com/writeas/writeas-cli/cmd/writeas && -echo "Success." diff --git a/meson/post_install.py b/meson/post_install.py index 7634127..f0e9ea9 100644 --- a/meson/post_install.py +++ b/meson/post_install.py @@ -1,22 +1,15 @@ #!/usr/bin/env python3 -import os +from os import path, environ import subprocess -prefix = os.environ.get('MESON_INSTALL_PREFIX', '/usr/local') -datadir = os.path.join(prefix, 'share') +prefix = environ.get('MESON_INSTALL_PREFIX', '/usr/local') +schemadir = path.join(environ['MESON_INSTALL_PREFIX'], 'share', 'glib-2.0', 'schemas') +datadir = path.join(prefix, 'share') +desktop_database_dir = path.join(datadir, 'applications') -# Packaging tools define DESTDIR and this isn't needed for them -if 'DESTDIR' not in os.environ: - print('Updating icon cache...') - icon_cache_dir = os.path.join(datadir, 'icons', 'hicolor') - if not os.path.exists(icon_cache_dir): - os.makedirs(icon_cache_dir) - subprocess.call(['gtk-update-icon-cache', '-qtf', icon_cache_dir]) - - print('Updating desktop database...') - desktop_database_dir = os.path.join(datadir, 'applications') - if not os.path.exists(desktop_database_dir): - os.makedirs(desktop_database_dir) +if not environ.get('DESTDIR'): + print('Updating desktop database…') subprocess.call(['update-desktop-database', '-q', desktop_database_dir]) - + print('Updating icon cache…') + subprocess.call(['gtk-update-icon-cache', '-qtf', path.join(datadir, 'icons', 'hicolor')]) \ No newline at end of file diff --git a/meson_options.txt b/meson_options.txt deleted file mode 100644 index 29bd56e..0000000 --- a/meson_options.txt +++ /dev/null @@ -1 +0,0 @@ -option('platform', type: 'combo', choices: ['default', 'elementary'], value: 'default') diff --git a/src/application.vala b/src/Application.vala similarity index 92% rename from src/application.vala rename to src/Application.vala index 848daa9..9936061 100644 --- a/src/application.vala +++ b/src/Application.vala @@ -1,44 +1,41 @@ /* 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; -extern const string BUILD_PLATFORM; - public class WriteAs.Application : Gtk.Application { construct { this.flags |= ApplicationFlags.HANDLES_OPEN; Intl.setlocale(LocaleCategory.ALL, ""); Intl.textdomain("write.as"); - application_id = APP_ID + ".desktop"; + application_id = "com.github.writeas.writeas-gtk" + ".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/window.vala b/src/Window.vala similarity index 98% rename from src/window.vala rename to src/Window.vala index 2b115a5..c6d8b08 100644 --- a/src/window.vala +++ b/src/Window.vala @@ -1,419 +1,416 @@ /* 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 = APP_ID; + icon_name = "com.github.writeas.writeas-gtk"; 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 icon_size = Gtk.IconSize.SMALL_TOOLBAR; - if (BUILD_PLATFORM == "elementary") { - icon_size = Gtk.IconSize.LARGE_TOOLBAR; - } + var icon_size = Gtk.IconSize.LARGE_TOOLBAR; var publish_button = new Gtk.Button.from_icon_name("document-send", icon_size); 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", icon_size); 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} diff --git a/src/meson.build b/src/meson.build index 75de727..ff4695e 100644 --- a/src/meson.build +++ b/src/meson.build @@ -1,11 +1,6 @@ -executable(app_id, - 'application.vala', - 'window.vala', +sources = files( + '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 + 'Granite/ModeSwitch.vala' )