diff --git a/build-aux/meson/postinstall.py b/build-aux/meson/postinstall.py new file mode 100644 index 0000000..6a3ea97 --- /dev/null +++ b/build-aux/meson/postinstall.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 + +from os import environ, path +from subprocess import call + +prefix = environ.get('MESON_INSTALL_PREFIX', '/usr/local') +datadir = path.join(prefix, 'share') +destdir = environ.get('DESTDIR', '') + +# Package managers set this so we don't need to run +if not destdir: + print('Updating icon cache...') + call(['gtk-update-icon-cache', '-qtf', path.join(datadir, 'icons', 'hicolor')]) + + print('Updating desktop database...') + call(['update-desktop-database', '-q', path.join(datadir, 'applications')]) + + print('Compiling GSettings schemas...') + call(['glib-compile-schemas', path.join(datadir, 'glib-2.0', 'schemas')]) + + diff --git a/data/meson.build b/data/meson.build new file mode 100644 index 0000000..974b626 --- /dev/null +++ b/data/meson.build @@ -0,0 +1,41 @@ +desktop_file = i18n.merge_file( + input: 'org.gnome.Ddgtk.desktop.in', + output: 'org.gnome.Ddgtk.desktop', + type: 'desktop', + po_dir: '../po', + install: true, + install_dir: join_paths(get_option('datadir'), 'applications') +) + +desktop_utils = find_program('desktop-file-validate', required: false) +if desktop_utils.found() + test('Validate desktop file', desktop_utils, + args: [desktop_file] + ) +endif + +appstream_file = i18n.merge_file( + input: 'org.gnome.Ddgtk.appdata.xml.in', + output: 'org.gnome.Ddgtk.appdata.xml', + po_dir: '../po', + install: true, + install_dir: join_paths(get_option('datadir'), 'appdata') +) + +appstream_util = find_program('appstream-util', required: false) +if appstream_util.found() + test('Validate appstream file', appstream_util, + args: ['validate', appstream_file] + ) +endif + +install_data('org.gnome.Ddgtk.gschema.xml', + install_dir: join_paths(get_option('datadir'), 'glib-2.0/schemas') +) + +compile_schemas = find_program('glib-compile-schemas', required: false) +if compile_schemas.found() + test('Validate schema file', compile_schemas, + args: ['--strict', '--dry-run', meson.current_source_dir()] + ) +endif diff --git a/data/org.gnome.Ddgtk.appdata.xml.in b/data/org.gnome.Ddgtk.appdata.xml.in new file mode 100644 index 0000000..116a8b1 --- /dev/null +++ b/data/org.gnome.Ddgtk.appdata.xml.in @@ -0,0 +1,7 @@ + + + org.gnome.Ddgtk.desktop + CC0 + + + diff --git a/data/org.gnome.Ddgtk.desktop.in b/data/org.gnome.Ddgtk.desktop.in new file mode 100644 index 0000000..3e10bd0 --- /dev/null +++ b/data/org.gnome.Ddgtk.desktop.in @@ -0,0 +1,7 @@ +[Desktop Entry] +Name=ddgtk +Exec=ddgtk +Terminal=false +Type=Application +Categories=GTK; +StartupNotify=true diff --git a/data/org.gnome.Ddgtk.gschema.xml b/data/org.gnome.Ddgtk.gschema.xml new file mode 100644 index 0000000..7653ce4 --- /dev/null +++ b/data/org.gnome.Ddgtk.gschema.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/meson.build b/meson.build new file mode 100644 index 0000000..34bad8a --- /dev/null +++ b/meson.build @@ -0,0 +1,11 @@ +project('ddgtk', + version: '0.1.0', + meson_version: '>= 0.40.0', +) +i18n = import('i18n') + + +subdir('data') +subdir('src') +subdir('po') +meson.add_install_script('build-aux/meson/postinstall.py') diff --git a/po/POTFILES b/po/POTFILES new file mode 100644 index 0000000..c771413 --- /dev/null +++ b/po/POTFILES @@ -0,0 +1,9 @@ +data/org.gnome.Ddgtk.desktop.in +data/org.gnome.Ddgtk.appdata.xml.in +data/org.gnome.Ddgtk.gschema.xml +src/window.ui + + + +src/main.py +src/window.py diff --git a/po/meson.build b/po/meson.build new file mode 100644 index 0000000..0e8688b --- /dev/null +++ b/po/meson.build @@ -0,0 +1 @@ +i18n.gettext('ddgtk', preset: 'glib') diff --git a/src/ddgtk.gresource.xml b/src/ddgtk.gresource.xml new file mode 100644 index 0000000..3adab00 --- /dev/null +++ b/src/ddgtk.gresource.xml @@ -0,0 +1,6 @@ + + + + window.ui + + diff --git a/src/ddgtk.in b/src/ddgtk.in new file mode 100644 index 0000000..61130a9 --- /dev/null +++ b/src/ddgtk.in @@ -0,0 +1,41 @@ +#!@PYTHON@ + +# ddgtk.in +# +# Copyright (C) 2017 Alessandro +# +# 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 . + +import os +import sys +import signal +import gettext + +VERSION = '@VERSION@' +pkgdatadir = '@pkgdatadir@' +localedir = '@localedir@' + +sys.path.insert(1, pkgdatadir) +signal.signal(signal.SIGINT, signal.SIG_DFL) +gettext.install('ddgtk', localedir) + +if __name__ == '__main__': + import gi + + from gi.repository import Gio + resource = Gio.Resource.load(os.path.join(pkgdatadir, 'ddgtk.gresource')) + resource._register() + + from ddgtk import main + sys.exit(main.main()) diff --git a/src/gi_composites.py b/src/gi_composites.py new file mode 100644 index 0000000..857e584 --- /dev/null +++ b/src/gi_composites.py @@ -0,0 +1,273 @@ +# +# Copyright (C) 2015 Dustin Spicuzza +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 +# USA + +from os.path import abspath, join + +import inspect +import warnings + +from gi.repository import Gio +from gi.repository import GLib +from gi.repository import GObject +from gi.repository import Gtk + +__all__ = ['GtkTemplate'] + +class GtkTemplateWarning(UserWarning): + pass + +def _connect_func(builder, obj, signal_name, handler_name, + connect_object, flags, cls): + '''Handles GtkBuilder signal connect events''' + + if connect_object is None: + extra = () + else: + extra = (connect_object,) + + # The handler name refers to an attribute on the template instance, + # so ask GtkBuilder for the template instance + template_inst = builder.get_object(cls.__gtype_name__) + + if template_inst is None: # This should never happen + errmsg = "Internal error: cannot find template instance! obj: %s; " \ + "signal: %s; handler: %s; connect_obj: %s; class: %s" % \ + (obj, signal_name, handler_name, connect_object, cls) + warnings.warn(errmsg, GtkTemplateWarning) + return + + handler = getattr(template_inst, handler_name) + + if flags == GObject.ConnectFlags.AFTER: + obj.connect_after(signal_name, handler, *extra) + else: + obj.connect(signal_name, handler, *extra) + + template_inst.__connected_template_signals__.add(handler_name) + + +def _register_template(cls, template_bytes): + '''Registers the template for the widget and hooks init_template''' + + # This implementation won't work if there are nested templates, but + # we can't do that anyways due to PyGObject limitations so it's ok + + if not hasattr(cls, 'set_template'): + raise TypeError("Requires PyGObject 3.13.2 or greater") + + cls.set_template(template_bytes) + + bound_methods = set() + bound_widgets = set() + + # Walk the class, find marked callbacks and child attributes + for name in dir(cls): + + o = getattr(cls, name, None) + + if inspect.ismethod(o): + if hasattr(o, '_gtk_callback'): + bound_methods.add(name) + # Don't need to call this, as connect_func always gets called + #cls.bind_template_callback_full(name, o) + elif isinstance(o, _Child): + cls.bind_template_child_full(name, True, 0) + bound_widgets.add(name) + + # Have to setup a special connect function to connect at template init + # because the methods are not bound yet + cls.set_connect_func(_connect_func, cls) + + cls.__gtemplate_methods__ = bound_methods + cls.__gtemplate_widgets__ = bound_widgets + + base_init_template = cls.init_template + cls.init_template = lambda s: _init_template(s, cls, base_init_template) + + +def _init_template(self, cls, base_init_template): + '''This would be better as an override for Gtk.Widget''' + + # TODO: could disallow using a metaclass.. but this is good enough + # .. if you disagree, feel free to fix it and issue a PR :) + if self.__class__ is not cls: + raise TypeError("Inheritance from classes with @GtkTemplate decorators " + "is not allowed at this time") + + connected_signals = set() + self.__connected_template_signals__ = connected_signals + + base_init_template(self) + + for name in self.__gtemplate_widgets__: + widget = self.get_template_child(cls, name) + self.__dict__[name] = widget + + if widget is None: + # Bug: if you bind a template child, and one of them was + # not present, then the whole template is broken (and + # it's not currently possible for us to know which + # one is broken either -- but the stderr should show + # something useful with a Gtk-CRITICAL message) + raise AttributeError("A missing child widget was set using " + "GtkTemplate.Child and the entire " + "template is now broken (widgets: %s)" % + ', '.join(self.__gtemplate_widgets__)) + + for name in self.__gtemplate_methods__.difference(connected_signals): + errmsg = ("Signal '%s' was declared with @GtkTemplate.Callback " + + "but was not present in template") % name + warnings.warn(errmsg, GtkTemplateWarning) + + +# TODO: Make it easier for IDE to introspect this +class _Child(object): + ''' + Assign this to an attribute in your class definition and it will + be replaced with a widget defined in the UI file when init_template + is called + ''' + + __slots__ = [] + + @staticmethod + def widgets(count): + ''' + Allows declaring multiple widgets with less typing:: + + button \ + label1 \ + label2 = GtkTemplate.Child.widgets(3) + ''' + return [_Child() for _ in range(count)] + + +class _GtkTemplate(object): + ''' + Use this class decorator to signify that a class is a composite + widget which will receive widgets and connect to signals as + defined in a UI template. You must call init_template to + cause the widgets/signals to be initialized from the template:: + + @GtkTemplate(ui='foo.ui') + class Foo(Gtk.Box): + + def __init__(self): + super(Foo, self).__init__() + self.init_template() + + The 'ui' parameter can either be a file path or a GResource resource + path:: + + @GtkTemplate(ui='/org/example/foo.ui') + class Foo(Gtk.Box): + pass + + To connect a signal to a method on your instance, do:: + + @GtkTemplate.Callback + def on_thing_happened(self, widget): + pass + + To create a child attribute that is retrieved from your template, + add this to your class definition:: + + @GtkTemplate(ui='foo.ui') + class Foo(Gtk.Box): + + widget = GtkTemplate.Child() + + + Note: This is implemented as a class decorator, but if it were + included with PyGI I suspect it might be better to do this + in the GObject metaclass (or similar) so that init_template + can be called automatically instead of forcing the user to do it. + + .. note:: Due to limitations in PyGObject, you may not inherit from + python objects that use the GtkTemplate decorator. + ''' + + __ui_path__ = None + + @staticmethod + def Callback(f): + ''' + Decorator that designates a method to be attached to a signal from + the template + ''' + f._gtk_callback = True + return f + + + Child = _Child + + @staticmethod + def set_ui_path(*path): + ''' + If using file paths instead of resources, call this *before* + loading anything that uses GtkTemplate, or it will fail to load + your template file + + :param path: one or more path elements, will be joined together + to create the final path + + TODO: Alternatively, could wait until first class instantiation + before registering templates? Would need a metaclass... + ''' + _GtkTemplate.__ui_path__ = abspath(join(*path)) + + + def __init__(self, ui): + self.ui = ui + + def __call__(self, cls): + + if not issubclass(cls, Gtk.Widget): + raise TypeError("Can only use @GtkTemplate on Widgets") + + # Nested templates don't work + if hasattr(cls, '__gtemplate_methods__'): + raise TypeError("Cannot nest template classes") + + # Load the template either from a resource path or a file + # - Prefer the resource path first + + try: + template_bytes = Gio.resources_lookup_data(self.ui, Gio.ResourceLookupFlags.NONE) + except GLib.GError: + ui = self.ui + if isinstance(ui, (list, tuple)): + ui = join(ui) + + if _GtkTemplate.__ui_path__ is not None: + ui = join(_GtkTemplate.__ui_path__, ui) + + with open(ui, 'rb') as fp: + template_bytes = GLib.Bytes.new(fp.read()) + + _register_template(cls, template_bytes) + return cls + + + +# Future shim support if this makes it into PyGI? +#if hasattr(Gtk, 'GtkTemplate'): +# GtkTemplate = lambda c: c +#else: +GtkTemplate = _GtkTemplate + diff --git a/ddgtk.py b/src/main.py similarity index 86% rename from ddgtk.py rename to src/main.py index cf9457d..5e76611 100644 --- a/ddgtk.py +++ b/src/main.py @@ -1,7 +1,22 @@ -#!/usr/bin/python +# main.py +# +# Copyright (C) 2017 Alessandro +# +# 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 . import gi -gi.require_version('Gtk', '3.0') +gi.require_version('Gtk', '3.0') gi.require_version('Vte', '2.91') from gi.repository import Gtk, Vte, GLib @@ -21,21 +36,16 @@ def get_data(): for i in range(len(now)): new_list.append(i) my_dict=dict(zip(new_list,now)) - + return my_dict -class launcher: - def __init__(self): - - self.terminal = Vte.Terminal() +class Application(): + def __init__(self): self.builder = Gtk.Builder() - #GObject.type_register(Vte.Terminal) - - self.builder.add_from_file("/home/alessandro/Projects/ddgtk/ddgtk.glade") - + self.builder.add_from_resource('/org/gnome/Ddgtk/window.ui') self.builder.connect_signals(self) - + self.terminal = Vte.Terminal() self.window = self.builder.get_object('window') self.window.connect('destroy', lambda w: Gtk.main_quit()) icontheme = Gtk.IconTheme.get_default() @@ -74,7 +84,6 @@ def __init__(self): None, None, ) - #self.terminal.show() def on_file_set(self,widget): print(self.file.get_filename()) self.filename=self.file.get_filename() @@ -91,7 +100,7 @@ def on_combo_changed(self,widget): self.device=self.device.split(' ')[0] call(["lsblk",self.device]) def on_start_clicked(self,widget): - + if (self.file.get_filename()) == None: print("No file selected") self.no_file_message.show() @@ -112,18 +121,20 @@ def dd(self): wait="echo 'Writing to disk please wait!'\n" self.umount= "umount " + self.device+"*" + " &>/dev/null \n" self.command = "pkexec dd if="+self.filename+" of="+self.device+" status=progress && sync;exit \n" + self.terminal.feed_child(clear,-1) self.terminal.feed_child(wait,-1) self.terminal.feed_child(self.umount,-1) #self.terminal.feed_child(cmd,-1) + self.terminal.unselect_all() self.terminal.feed_child(self.command,-1) - + #self.terminal.set_input_enabled(False) - + def on_expander_activate(self,widget): self.terminal.set_rewrap_on_resize(True) self.terminal.show() - + def on_no_file_button_clicked(self,widget): self.no_file_message.hide() def on_done_button_clicked(self,widget): @@ -144,7 +155,7 @@ def on_confirm_ok_clicked(self,widget): def on_confirm_cancel_clicked(self,widget): self.confirm.hide() return - + def done (self,terminal,a): if a is not 0: print("Authentication error or command error") @@ -154,13 +165,9 @@ def done (self,terminal,a): print("All done") self.done_button.set_visible(True) self.create_disk_label.set_text("Finshed!\n Click the done Button :)") - - -def main(): - launcher() - Gtk.main() -if __name__ == '__main__': - main() +def main(): + Application() + Gtk.main() diff --git a/src/meson.build b/src/meson.build new file mode 100644 index 0000000..24120fb --- /dev/null +++ b/src/meson.build @@ -0,0 +1,38 @@ +pkgdatadir = join_paths(get_option('prefix'), get_option('datadir'), meson.project_name()) +moduledir = join_paths(pkgdatadir, 'ddgtk') +gnome = import('gnome') + +gnome.compile_resources('ddgtk', + 'ddgtk.gresource.xml', + gresource_bundle: true, + install: true, + install_dir: pkgdatadir, +) + +python3 = import('python3') + +conf = configuration_data() +conf.set('PYTHON', python3.find_python().path()) +conf.set('VERSION', meson.project_version()) +conf.set('localedir', join_paths(get_option('prefix'), get_option('localedir'))) +conf.set('pkgdatadir', pkgdatadir) +r= run_command('chmod','755','ddgtk.in') +if r.returncode() != 0 + # it failed +endif +configure_file( + input: 'ddgtk.in', + output: 'ddgtk', + configuration: conf, + install: true, + install_dir: get_option('bindir'), + +) + + +ddgtk_sources = [ + 'gi_composites.py', + 'main.py', +] + +install_data(ddgtk_sources, install_dir: moduledir) diff --git a/ddgtk.glade b/src/window.ui similarity index 100% rename from ddgtk.glade rename to src/window.ui