├── po
├── LINGUAS
├── POTFILES.in
├── meson.build
└── icon-requests.pot
├── meson_options.txt
├── screenshots
├── screenshot1.png
├── screenshot2.png
└── screenshot3.png
├── data
├── icons
│ └── meson.build
├── css
│ └── style.css
├── issue.model
├── icon-requests.gresource.xml
├── icon-requests.desktop.in
├── repos.json
├── org.gnome.IconRequests.gschema.xml
├── icon-requests.appdata.xml.in
├── meson.build
└── ui
│ ├── about.ui
│ └── mainwindow.ui
├── meson_post_install.sh
├── .travis.yml
├── Dockerfile
├── IconRequests
├── widgets
│ ├── notification.py
│ ├── application_row.py
│ └── window.py
├── modules
│ ├── settings.py
│ ├── repos.py
│ ├── upload
│ │ ├── upload.py
│ │ └── imgur.py
│ └── desktop.py
├── const.py
├── application.py
└── utils.py
├── README.md
├── .gitignore
├── update_pot.sh
├── icon-requests.in
├── meson.build
└── LICENSE
/po/LINGUAS:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/po/POTFILES.in:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/meson_options.txt:
--------------------------------------------------------------------------------
1 | option('post_install', type : 'boolean', value : 'true')
2 |
--------------------------------------------------------------------------------
/screenshots/screenshot1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bilelmoussaoui/Icon-Requests/HEAD/screenshots/screenshot1.png
--------------------------------------------------------------------------------
/screenshots/screenshot2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bilelmoussaoui/Icon-Requests/HEAD/screenshots/screenshot2.png
--------------------------------------------------------------------------------
/screenshots/screenshot3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bilelmoussaoui/Icon-Requests/HEAD/screenshots/screenshot3.png
--------------------------------------------------------------------------------
/data/icons/meson.build:
--------------------------------------------------------------------------------
1 | icon_themes = ['hicolor']
2 | foreach theme : icon_themes
3 | install_subdir(theme, install_dir: 'share/icons/')
4 | endforeach
5 |
--------------------------------------------------------------------------------
/meson_post_install.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | # Update icon caches
4 | gtk-update-icon-cache -f -t ${DESTDIR}/${MESON_INSTALL_PREFIX}/share/icons/hicolor
5 |
6 | # Install new schemas
7 | glib-compile-schemas ${DESTDIR}/${MESON_INSTALL_PREFIX}/share/glib-2.0/schemas/
8 |
9 |
--------------------------------------------------------------------------------
/po/meson.build:
--------------------------------------------------------------------------------
1 | i18n = import('i18n')
2 | message('Update translations')
3 |
4 | r = run_command('cat', 'LINGUAS')
5 | if r.returncode() != 0
6 | error('Cannot find po/LINGUAS')
7 | endif
8 |
9 | langs = r.stdout().strip().split()
10 | i18n.gettext('icon-requests', languages : langs)
11 |
--------------------------------------------------------------------------------
/data/css/style.css:
--------------------------------------------------------------------------------
1 | .application-path {
2 | font-size: 11px;
3 | }
4 |
5 | .application-label{
6 | font-size: 11px;
7 | font-style: italic;
8 | }
9 |
10 | .application-name {
11 | font-weight: bold;
12 | font-size: 12px;
13 | }
14 |
15 | .applications-list {
16 | background: white;
17 | }
18 |
--------------------------------------------------------------------------------
/data/issue.model:
--------------------------------------------------------------------------------
1 | | Application name | Icon name | Desktop file name | Original icon | Theme |
2 | | --- | :-- | :-- | :-- | :-- |
3 | | {app.name} | `{app.icon}` | `{app.desktop}` | | {theme} |
4 |
5 | This issue was automatically created using [IconRequests](https://github.com/bil-elmoussaoui/Icon-Requests).
6 |
--------------------------------------------------------------------------------
/data/icon-requests.gresource.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | css/style.css
5 | repos.json
6 | issue.model
7 | ui/about.ui
8 | ui/mainwindow.ui
9 |
10 |
11 |
--------------------------------------------------------------------------------
/data/icon-requests.desktop.in:
--------------------------------------------------------------------------------
1 | [Desktop Entry]
2 | Name=Icon Requests
3 | GenericName=Missing Icon Requests
4 | Comment=Report Missing icons to your current theme
5 | Icon=icon-requests
6 | Exec=icon-requests
7 | Type=Application
8 | Terminal=false
9 | Categories=GNOME;GTK;
10 | Keywords=Gnome;GTK;Verification;
11 | StartupNotify=true
12 | X-GNOME-Gettext-Domain=icon-requests
13 | StartupWMClass=icon-requests
14 | Actions=AboutDialog;
15 |
16 | [Desktop Action AboutDialog]
17 | Exec=icon-requests --about
18 | Name=About Icon Requests
19 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: required
2 |
3 | language: generic
4 |
5 | services:
6 | - docker
7 |
8 | before_install:
9 | - docker pull fedora:25
10 |
11 | script:
12 | - docker run --privileged -it fedora:25 /bin/sh -c "dnf -y install git intltool python3-pip python3-cairosvg inkscape glib libgobject; dnf -y group install 'Development Tools'; pip3 install --upgrade pip; pip3 install meson ninja requests Pillow;git clone https://github.com/bil-elmoussaoui/Icon-Requests; cd ./Icon-Requests; mkdir build; cd ./build; meson ..; ninja;ninja install; icon-requests"
13 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM ubuntu:16.04
2 | RUN apt-get -y update
3 | # Install dependecies
4 | RUN apt-get install -y git python-gobject ninja python3-cairosvg python3-gi python3-pip python-dev libgtk-3-dev intltool gobject-introspection libgirepository1.0-dev gir1.2-gtk-3.0
5 | RUN pip3 install requests Pillow meson
6 |
7 | # Build Icon Requests using meson
8 | RUN git clone https://github.com/bil-elmoussaoui/Icon-Requests
9 | WORKDIR ./Icon-Requests
10 | RUN mkdir build
11 | WORKDIR ./build
12 | RUN meson .. && ninja && sudo ninja install
13 |
14 | CMD icon-requests --debug
15 |
--------------------------------------------------------------------------------
/data/repos.json:
--------------------------------------------------------------------------------
1 | {
2 | "Paper, Paper-Mono-Dark": {
3 | "url": "https://github.com/snwh/paper-icon-theme"
4 | },
5 | "Numix-Circle, Numix-Circle-Light, Numix-Square, Numix-Square-Light": {
6 | "url": "https://github.com/numixproject/numix-core"
7 | },
8 | "Papirus-Dark, Papirus-Light, Papirus, ePapirus": {
9 | "url": "https://github.com/PapirusDevelopmentTeam/papirus-icon-theme"
10 | },
11 | "la-capitaine-icon-theme": {
12 | "url": "https://github.com/keeferrourke/la-capitaine-icon-theme"
13 | },
14 | "elementaryPlus": {
15 | "url": "https://github.com/mank319/elementaryPlus"
16 | },
17 | "Oranchelo, Oranchelo-Green": {
18 | "url": "https://github.com/OrancheloTeam/oranchelo-icon-theme"
19 | }
20 | }
--------------------------------------------------------------------------------
/IconRequests/widgets/notification.py:
--------------------------------------------------------------------------------
1 | from gi import require_version
2 | require_version("Gtk", "3.0")
3 | from gi.repository import Gtk
4 |
5 | class Notification:
6 |
7 | def __init__(self, notification, notification_label):
8 | self.notification = notification
9 | self.label = notification_label
10 | self.notification.connect("response", self.response)
11 |
12 | def set_message(self, message):
13 | self.label.set_text(message)
14 |
15 | def response(self, *args):
16 | self.hide()
17 |
18 | def set_type(self, type):
19 | if not isinstance(type, Gtk.MessageType):
20 | type = Gtk.MessageType.INFO
21 | self.notification.set_message_type(type)
22 |
23 | def show(self):
24 | self.notification.get_parent().set_reveal_child(True)
25 |
26 | def hide(self):
27 | self.notification.get_parent().set_reveal_child(False)
28 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Icon Requests
2 | A Gtk application to report missing icons to your current theme github repository
3 |
4 | ### Screenshots
5 |
6 |
7 |
8 | ### Install
9 | - Archlinux (AUR):
10 | ```bash
11 | yaourt -S icon-requests
12 | ```
13 |
14 |
15 | ### Build from source code
16 | 1 - Clone the repository
17 | ```bash
18 | git clone https://github.com/bil-elmoussaoui/Icon-Requests && cd ./Icon-Requests
19 | ```
20 | 2 - Install dependecies
21 | - meson
22 | - ninja
23 | - requests
24 | - python3-cairosvg or inkscape
25 | - Pillow
26 | - Gtk 3.16+
27 | - Python 3.3+
28 |
29 | 3 - Build the application
30 | ```bash
31 | mkdir build && cd ./build
32 | meson ..
33 | ninja
34 | sudo ninja install
35 | ```
36 | 4 - Run the application!
37 |
--------------------------------------------------------------------------------
/IconRequests/modules/settings.py:
--------------------------------------------------------------------------------
1 | from gi.repository import Gio, GLib
2 |
3 |
4 | class Settings(Gio.Settings):
5 |
6 | def __init__(self):
7 | Gio.Settings.__init__(self)
8 |
9 | def new():
10 | gsettings = Gio.Settings.new("org.gnome.IconRequests")
11 | gsettings.__class__ = Settings
12 | return gsettings
13 |
14 | def get_window_position(self):
15 | return tuple(self.get_value('window-position'))
16 |
17 | def set_window_postion(self, position):
18 | position = GLib.Variant('ai', list(position))
19 | self.set_value('window-position', position)
20 |
21 | def set_is_night_mode(self, statue):
22 | self.set_boolean('night-mode', statue)
23 |
24 | def get_is_night_mode(self):
25 | return self.get_boolean('night-mode')
26 |
27 | def get_imgur_client_id(self):
28 | return self.get_string("imgur-client-id")
29 |
--------------------------------------------------------------------------------
/data/org.gnome.IconRequests.gschema.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | [0,0]
6 | Default window postiton
7 |
8 | Default window postiton
9 |
10 |
11 |
12 | false
13 | Night mode
14 |
15 | Enable/disable night mode within the application
16 |
17 |
18 |
19 | "01fb07fcc6cc0fe"
20 | imgur client id
21 | don't change that only if you have a proper one
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/IconRequests/const.py:
--------------------------------------------------------------------------------
1 | from os import path
2 | from gi.repository import GLib
3 | from IconRequests.modules.settings import Settings
4 | from IconRequests.modules.repos import Repositories
5 |
6 | SUPPORTED_ICONS = []
7 |
8 | settings = Settings.new()
9 | repositories = Repositories()
10 | ICONS_IGNORE_LIST = [""]
11 |
12 | # This is only needed for verifing if the issue already exists!
13 | # Which means that the application will fetch the latest 400 open issues.
14 | ISSUES_PER_PAGE = 100
15 | NB_PAGES = 4
16 |
17 |
18 | DESKTOP_FILE_DIRS = ["/usr/share/applications/",
19 | "/usr/share/applications/kde4/",
20 | "/usr/share/applications/wine/",
21 | "/usr/local/share/applications/",
22 | "/usr/local/share/applications/kde4/",
23 | "/usr/local/share/applications/wine/",
24 | "%s/.local/share/applications/" % GLib.get_home_dir(),
25 | "%s/.local/share/applications/kde4/" % GLib.get_home_dir(),
26 | "%s/.local/share/applications/wine/" % GLib.get_home_dir(),
27 | GLib.get_user_special_dir(GLib.UserDirectory.DIRECTORY_DESKTOP)]
28 |
--------------------------------------------------------------------------------
/data/icon-requests.appdata.xml.in:
--------------------------------------------------------------------------------
1 |
2 |
3 | icon-requests.desktop
4 | GPL3
5 | <_name>Icon Requests
6 | <_summary>missing icons reporter
7 |
8 |
9 | https://raw.githubusercontent.com/bil-elmoussaoui/Icon-Requests/master/screenshots/screenshot1.png
10 |
11 |
12 | https://raw.githubusercontent.com/bil-elmoussaoui/Icon-Requests/master/screenshots/screenshot2.png
13 |
14 |
15 | https://raw.githubusercontent.com/bil-elmoussaoui/Icon-Requests/master/screenshots/screenshot3.png
16 |
17 |
18 |
19 | <_p>Simple application to report missing icons to github repository
20 |
21 | https://github.com/bil-elmoussaoui/Icon-Requests
22 | https://github.com/bil-elmoussaoui/Icon-Requests
23 | bil.elmoussaoui@gmail.com
24 | icon-requests
25 |
26 |
--------------------------------------------------------------------------------
/IconRequests/modules/repos.py:
--------------------------------------------------------------------------------
1 | from gi.repository import Gio, GLib
2 | from json import loads
3 | from urllib.parse import urlsplit
4 | import logging
5 |
6 | class Repositories:
7 |
8 | def __init__(self):
9 | try:
10 | repos_obj = Gio.File.new_for_uri('resource:///org/gnome/IconRequests/repos.json')
11 | self._repositories = loads(str(repos_obj.load_contents(None)[1].decode("utf-8")))
12 | self._get_supported_themes()
13 | except GLib.Error:
14 | logging.error("Error loading repository database file. Exiting")
15 | exit()
16 |
17 | def is_supported(self, theme):
18 | return theme in self._supported_themes
19 |
20 | def get_repo(self, theme):
21 | url = self.get_url(theme)
22 | return urlsplit(url).path.strip("/")
23 |
24 | def get_url(self, theme):
25 | key = self._get_key(theme)
26 | return self._repositories[key]["url"]
27 |
28 | def _get_key(self, theme):
29 | for key in self._repositories:
30 | if theme in key:
31 | return key
32 | return None
33 |
34 | def _get_supported_themes(self):
35 | supported = []
36 | for key in self._repositories:
37 | supported.extend(key.split(","))
38 | supported = list(map(lambda x: x.strip(), supported))
39 | self._supported_themes = supported
40 |
--------------------------------------------------------------------------------
/data/meson.build:
--------------------------------------------------------------------------------
1 | gnome = import('gnome')
2 |
3 | build_dir = get_option('prefix') + '/' + get_option('datadir')
4 | po_dir = get_option('prefix') + '/' + get_option('datadir') + '/locale'
5 | message('TODO: Compiling resources')
6 | gnome.compile_resources(
7 | 'icon-requests', 'icon-requests.gresource.xml',
8 | gresource_bundle: true,
9 | source_dir : '.',
10 | install_dir : build_dir + '/IconRequests',
11 | install: true)
12 |
13 |
14 | message('Compiling schemas')
15 | gnome.compile_schemas()
16 | install_data(
17 | 'org.gnome.IconRequests.gschema.xml',
18 | install_dir : 'share/glib-2.0/schemas')
19 |
20 | message('TODO: Making a .desktop file')
21 | custom_target('icon-requests.desktop',
22 | output : 'icon-requests.desktop',
23 | input : 'icon-requests.desktop.in',
24 | command : [intltool_merge, '--desktop-style', po_dir, '@INPUT@', '@OUTPUT@'],
25 | install : true,
26 | install_dir : get_option('datadir') + '/applications'
27 | )
28 |
29 | message('TODO: Preparing appdata')
30 | custom_target('icon-requests.appdata.xml',
31 | output : 'icon-requests.appdata.xml',
32 | input : 'icon-requests.appdata.xml.in',
33 | command : [intltool_merge, '--xml-style', po_dir, '@INPUT@', '@OUTPUT@'],
34 | install : true,
35 | install_dir : get_option('datadir') + '/appdata'
36 | )
37 |
38 | message('TODO: Making a list of icons')
39 | subdir('icons')
40 |
--------------------------------------------------------------------------------
/IconRequests/modules/upload/upload.py:
--------------------------------------------------------------------------------
1 | from os import path
2 | from IconRequests.utils import convert_svg2png
3 | from tempfile import NamedTemporaryFile
4 | from PIL import Image
5 |
6 | class Upload:
7 |
8 | @property
9 | def client_id(self):
10 | return self._client_id
11 |
12 | @client_id.setter
13 | def client_id(self, client_id):
14 | self._client_id = client_id
15 |
16 |
17 | def upload(self, icon_path, app_name=None):
18 | icon_extension = path.splitext(icon_path)[1].lower().strip(".")
19 | was_scaled = False
20 | if icon_extension in ["svg", "png"]:
21 | outfile = NamedTemporaryFile().name
22 | if icon_extension == "svg":
23 | convert_svg2png(icon_path, outfile, 48, 48)
24 | icon_path = outfile
25 | was_scaled = True
26 | icon_extension = "png"
27 | if icon_extension == "png":
28 | if not was_scaled:
29 | img = Image.open(icon_path)
30 | img = img.resize((48, 48), Image.ANTIALIAS)
31 | img.save(outfile, "PNG")
32 | icon_path = outfile
33 | icon_url = self.upload_icon(icon_path, app_name)
34 | return icon_url
35 | return None
36 |
37 | class ConnexionError(Exception):
38 | def __init__(self):
39 | super(ConnexionError, self).__init__()
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | env/
12 | build/
13 | develop-eggs/
14 | dist/
15 | downloads/
16 | eggs/
17 | .eggs/
18 | lib/
19 | lib64/
20 | parts/
21 | sdist/
22 | var/
23 | *.egg-info/
24 | .installed.cfg
25 | *.egg
26 |
27 | # PyInstaller
28 | # Usually these files are written by a python script from a template
29 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
30 | *.manifest
31 | *.spec
32 |
33 | # Installer logs
34 | pip-log.txt
35 | pip-delete-this-directory.txt
36 |
37 | # Unit test / coverage reports
38 | htmlcov/
39 | .tox/
40 | .coverage
41 | .coverage.*
42 | .cache
43 | nosetests.xml
44 | coverage.xml
45 | *,cover
46 | .hypothesis/
47 |
48 | # Translations
49 | *.mo
50 |
51 | # Django stuff:
52 | *.log
53 | local_settings.py
54 |
55 | # Flask stuff:
56 | instance/
57 | .webassets-cache
58 |
59 | # Scrapy stuff:
60 | .scrapy
61 |
62 | # Sphinx documentation
63 | docs/_build/
64 |
65 | # PyBuilder
66 | target/
67 |
68 | # IPython Notebook
69 | .ipynb_checkpoints
70 |
71 | # pyenv
72 | .python-version
73 |
74 | # celery beat schedule file
75 | celerybeat-schedule
76 |
77 | # dotenv
78 | .env
79 |
80 | # virtualenv
81 | venv/
82 | ENV/
83 |
84 | # Spyder project settings
85 | .spyderproject
86 |
87 | # Rope project settings
88 | .ropeproject
89 | *.ui~
90 | .idea/
91 | tags
--------------------------------------------------------------------------------
/IconRequests/modules/upload/imgur.py:
--------------------------------------------------------------------------------
1 | import requests
2 | from IconRequests.modules.settings import Settings
3 | from IconRequests.modules.upload.upload import Upload, ConnexionError
4 | from os import path
5 | from base64 import b64encode
6 |
7 | UPLOAD_URI = "https://api.imgur.com/3/upload"
8 |
9 | class Imgur(Upload):
10 |
11 | def __init__(self, settings):
12 | super(Imgur, self).__init__()
13 | self.client_id = settings.get_imgur_client_id()
14 |
15 | def upload_icon(self, image_path, title=None):
16 | if path.isfile(image_path):
17 | with open(image_path, 'rb') as image_obj:
18 | image_data = b64encode(image_obj.read())
19 | image_obj.close()
20 |
21 | headers = {
22 | "Authorization": "Client-ID {0}".format(self.client_id),
23 | "Accept": "application/json"
24 | }
25 | if not title:
26 | title = path.basename(image_path)
27 |
28 | data = {
29 | "type": "base64",
30 | "image": image_data,
31 | "title": title
32 | }
33 | try:
34 | query = requests.post(UPLOAD_URI, data, headers=headers)
35 | if query.status_code == 200:
36 | return query.json()["data"]["link"]
37 | else:
38 | raise ConnexionError
39 | except requests.exceptions.ConnectionError:
40 | raise ConnexionError
41 | return None
42 |
--------------------------------------------------------------------------------
/update_pot.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | # A modified code from the Terminix project.
3 | DOMAIN=icon-requests
4 | BASEDIR=$(dirname $0)
5 | OUTPUT_FILE=${BASEDIR}/po/${DOMAIN}.pot
6 |
7 | echo "Extracting translatable strings... "
8 |
9 | find ${BASEDIR}/ -name '*.py' | xgettext \
10 | --output $OUTPUT_FILE \
11 | --files-from=- \
12 | --directory=$BASEDIR \
13 | --language=Python \
14 | --keyword=C_:1c,2 \
15 | --from-code=utf-8
16 |
17 | find ${BASEDIR}/ -name '*.ui' | xgettext \
18 | --output $OUTPUT_FILE \
19 | --join-existing \
20 | --files-from=- \
21 | --directory=$BASEDIR \
22 | --language=Glade \
23 | --foreign-user \
24 | --keyword=C_:1c,2 \
25 | --from-code=utf-8
26 |
27 | xgettext \
28 | --join-existing \
29 | --output $OUTPUT_FILE \
30 | --default-domain=$DOMAIN \
31 | --package-name=$DOMAIN \
32 | --directory=$BASEDIR \
33 | --foreign-user \
34 | --language=Desktop \
35 | ${BASEDIR}/data/icon-requests.desktop.in
36 |
37 |
38 | xgettext \
39 | --join-existing \
40 | --output $OUTPUT_FILE \
41 | --default-domain=$DOMAIN \
42 | --package-name=$DOMAIN \
43 | --directory=$BASEDIR \
44 | --foreign-user \
45 | --language=appdata \
46 | ${BASEDIR}/data/icon-requests.appdata.xml.in
47 |
48 | # Merge the messages with existing po files
49 | po_files=$(find ${BASEDIR}/po/ -name "*.po")
50 | if [ ${#po_files[@]} != 0 ]; then
51 | echo "Merging with existing translations..."
52 | for file in $po_files
53 | do
54 | echo -n $file
55 | msgmerge --update $file $OUTPUT_FILE
56 | done
57 | fi
--------------------------------------------------------------------------------
/icon-requests.in:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 | import logging
4 | import signal
5 | import locale
6 | import gettext
7 | import argparse
8 | import faulthandler
9 | import sys
10 | from os import path
11 | from gi import require_version
12 | require_version("Gtk", "3.0")
13 | from gi.repository import Gtk, Gio
14 |
15 | sys.path.insert(1, '@pyexecdir@')
16 | sys.path.insert(1, '@pythondir@')
17 | # Load the gresource file first.
18 | resource = Gio.resource_load(path.join('@DATADIR@',
19 | 'icon-requests.gresource'))
20 | Gio.Resource._register(resource)
21 | from IconRequests.application import Application
22 |
23 | _ = gettext.gettext
24 |
25 | locale.bindtextdomain('icon-requests', "@localedir@")
26 | locale.textdomain('icon-requests')
27 | gettext.bindtextdomain('icon-requests', "@localedir@")
28 | gettext.textdomain('icon-requests')
29 |
30 | parser = argparse.ArgumentParser(prog="Icon Requests")
31 | parser.add_argument("--debug", "-d", action="store_true",
32 | help=_("Start in debug mode"))
33 | parser.add_argument("--version", "-v", action="store_true",
34 | help=_("Icon Requests version number"))
35 | parser.add_argument("--about", action="store_true",
36 | help=_("Show about dialog"))
37 | args = parser.parse_args()
38 |
39 | level = logging.ERROR
40 | if args.debug:
41 | level = logging.DEBUG
42 | faulthandler.enable()
43 | logging.basicConfig(level=level,
44 | format='[%(levelname)s] - %(asctime)s - %(message)s',
45 | datefmt='%Y-%m-%d %H:%M:%S')
46 |
47 |
48 |
49 | if args.version:
50 | sys.exit("Version : @VERSION@")
51 | elif args.about:
52 | about_dialog = Application.about_dialog()
53 | about_dialog.run()
54 | about_dialog.destroy()
55 | sys.exit()
56 | else:
57 | app = Application()
58 | try:
59 | exit_status = app.run(None)
60 | signal.signal(signal.SIGTERM, app.on_quit)
61 | signal.signal(signal.SIGINT, app.on_quit)
62 | sys.exit(exit_status)
63 | except KeyboardInterrupt:
64 | app.on_quit()
65 |
--------------------------------------------------------------------------------
/meson.build:
--------------------------------------------------------------------------------
1 | project('icon-requests', 'c', version: '0.1.2')
2 |
3 | message('Looking for dependencies')
4 | python = find_program('python3')
5 | git = find_program('git', required : false)
6 | intltool_merge = find_program('intltool-merge')
7 | if git.found()
8 | GITVERSION = run_command(git, 'rev-parse', 'HEAD').stdout().strip()
9 | else
10 | GITVERSION = ''
11 | endif
12 |
13 | glib = dependency('glib-2.0')
14 | gobject = dependency('gobject-2.0')
15 | gir = dependency('gobject-introspection-1.0')
16 | gtk = dependency('gtk+-3.0', version : '>=3.16')
17 |
18 | message('Getting python install dir')
19 | r = run_command(python, '-c', 'from distutils.sysconfig import get_python_lib; print(get_python_lib())')
20 | if r.returncode() != 0
21 | error('Cannot find python install dir')
22 | endif
23 | python_dir = r.stdout().strip()
24 | message('Python install dir found : YES ' + python_dir)
25 |
26 | # Configuration params
27 | conf = configuration_data()
28 | conf.set('PACKAGE_URL', 'https://github.com/bil-elmoussaoui/Icon-Requests')
29 | conf.set('LIBDIR', get_option('prefix') + '/' + get_option('libdir'))
30 | conf.set('DATADIR', get_option('prefix') + '/' + get_option('datadir') + '/IconRequests/')
31 | conf.set('LOCALE_DIR', get_option('prefix') + '/' + get_option('datadir') + '/locale/')
32 | conf.set('PACKAGE_NAME', 'Icon Requests')
33 | conf.set('PACKAGE', 'icon-requests')
34 | conf.set('GITVERSION', GITVERSION)
35 | conf.set('VERSION', meson.project_version())
36 | conf.set('BUILDDIR', meson.current_build_dir())
37 | conf.set('PYTHONDIR', python_dir)
38 |
39 | if get_option('post_install') == true
40 | post_install = 'enabled'
41 | else
42 | post_install = 'disabled'
43 | endif
44 | message('post install script is : ' + post_install)
45 |
46 | subdir('data')
47 | subdir('po')
48 |
49 |
50 | run_command(python, '-m', 'compileall', meson.current_source_dir() + '/IconRequests')
51 | install_subdir('IconRequests', install_dir: python_dir)
52 |
53 | message('Preparing init file')
54 | configure_file(input : 'icon-requests.in', output : 'icon-requests', configuration : conf)
55 |
56 | configure_file = '@0@/icon-requests'.format(meson.current_build_dir())
57 | install_data(configure_file, install_dir: 'bin', install_mode: 'rwxr-xr-x')
58 |
59 | if get_option('post_install') == true
60 | meson.add_install_script('meson_post_install.sh')
61 | endif
62 |
--------------------------------------------------------------------------------
/data/ui/about.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
50 |
51 |
--------------------------------------------------------------------------------
/po/icon-requests.pot:
--------------------------------------------------------------------------------
1 | # SOME DESCRIPTIVE TITLE.
2 | # This file is put in the public domain.
3 | # FIRST AUTHOR , YEAR.
4 | #
5 | #, fuzzy
6 | msgid ""
7 | msgstr ""
8 | "Project-Id-Version: icon-requests\n"
9 | "Report-Msgid-Bugs-To: \n"
10 | "POT-Creation-Date: 2017-03-21 12:13+0100\n"
11 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
12 | "Last-Translator: FULL NAME \n"
13 | "Language-Team: LANGUAGE \n"
14 | "Language: \n"
15 | "MIME-Version: 1.0\n"
16 | "Content-Type: text/plain; charset=CHARSET\n"
17 | "Content-Transfer-Encoding: 8bit\n"
18 |
19 | #: IconRequests/application.py:21 data/icon-requests.desktop.in:3
20 | msgid "Icon Requests"
21 | msgstr ""
22 |
23 | #: IconRequests/application.py:49
24 | msgid "Night Mode"
25 | msgstr ""
26 |
27 | #: IconRequests/application.py:50
28 | msgid "About"
29 | msgstr ""
30 |
31 | #: IconRequests/application.py:51
32 | msgid "Quit"
33 | msgstr ""
34 |
35 | #: IconRequests/widgets/application_row.py:81
36 | msgid "Report"
37 | msgstr ""
38 |
39 | #: IconRequests/widgets/application_row.py:94
40 | msgid "Fix"
41 | msgstr ""
42 |
43 | #: IconRequests/widgets/application_row.py:137
44 | msgid "Please check your connexion"
45 | msgstr ""
46 |
47 | #: IconRequests/widgets/application_row.py:141
48 | #: IconRequests/widgets/application_row.py:167
49 | msgid "Theme not supported"
50 | msgstr ""
51 |
52 | #: IconRequests/widgets/application_row.py:145
53 | msgid "You've reached your API limits"
54 | msgstr ""
55 |
56 | #: data/ui/about.ui:15 data/icon-requests.appdata.xml.in:19
57 | msgid "Simple application to report missing icons to github repository"
58 | msgstr ""
59 |
60 | #: data/ui/about.ui:16
61 | msgid ""
62 | "This program comes with absolutely no warranty. See the GNU General Public License, version 3 or "
64 | "later for details."
65 | msgstr ""
66 |
67 | #: data/ui/about.ui:21
68 | msgid "Github"
69 | msgstr ""
70 |
71 | #: data/ui/mainwindow.ui:46
72 | msgid "Loading desktop file informations..."
73 | msgstr ""
74 |
75 | #: data/ui/mainwindow.ui:90
76 | msgid "All"
77 | msgstr ""
78 |
79 | #: data/ui/mainwindow.ui:108
80 | msgid "Unsupported"
81 | msgstr ""
82 |
83 | #: data/ui/mainwindow.ui:127
84 | msgid "Hardcoded"
85 | msgstr ""
86 |
87 | #: data/icon-requests.desktop.in:4
88 | msgid "Missing Icon Requests"
89 | msgstr ""
90 |
91 | #: data/icon-requests.desktop.in:5
92 | msgid "Report Missing icons to your current theme"
93 | msgstr ""
94 |
95 | #: data/icon-requests.desktop.in:6
96 | msgid "icon-requests"
97 | msgstr ""
98 |
99 | #: data/icon-requests.desktop.in:11
100 | msgid "Gnome;GTK;Verification;"
101 | msgstr ""
102 |
103 | #: data/icon-requests.desktop.in:19
104 | msgid "About Icon Requests"
105 | msgstr ""
106 |
--------------------------------------------------------------------------------
/IconRequests/application.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 |
4 | from IconRequests.widgets.window import Window
5 | from IconRequests.utils import is_gnome, is_app_menu
6 | from IconRequests.const import settings
7 | from gettext import gettext as _
8 | import logging
9 | from gi import require_version
10 | require_version("Gtk", "3.0")
11 | from gi.repository import Gtk, GLib, Gio, Gdk, GObject
12 |
13 |
14 | class Application(Gtk.Application):
15 | win_object = None
16 |
17 | def __init__(self):
18 | Gtk.Application.__init__(self,
19 | application_id="org.gnome.IconRequests",
20 | flags=Gio.ApplicationFlags.FLAGS_NONE)
21 | GLib.set_application_name(_("Icon Requests"))
22 | GLib.set_prgname("Icon Requests")
23 |
24 |
25 | Gtk.Settings.get_default().set_property(
26 | "gtk-application-prefer-dark-theme", settings.get_is_night_mode())
27 |
28 | self.menu = Gio.Menu()
29 | cssProviderFile = Gio.File.new_for_uri(
30 | 'resource:///org/gnome/IconRequests/css/style.css')
31 | cssProvider = Gtk.CssProvider()
32 | screen = Gdk.Screen.get_default()
33 | styleContext = Gtk.StyleContext()
34 | try:
35 | cssProvider.load_from_file(cssProviderFile)
36 | styleContext.add_provider_for_screen(screen, cssProvider,
37 | Gtk.STYLE_PROVIDER_PRIORITY_USER)
38 | logging.debug("Loading css file ")
39 | except Exception as e:
40 | logging.error("Error message %s" % str(e))
41 |
42 | def do_startup(self):
43 | Gtk.Application.do_startup(self)
44 | self.generate_menu()
45 |
46 | def generate_menu(self):
47 | # Help section
48 | help_content = Gio.Menu.new()
49 | help_content.append_item(Gio.MenuItem.new(_("Night Mode"), "app.night_mode"))
50 | help_content.append_item(Gio.MenuItem.new(_("About"), "app.about"))
51 | help_content.append_item(Gio.MenuItem.new(_("Quit"), "app.quit"))
52 | help_section = Gio.MenuItem.new_section(None, help_content)
53 | self.menu.append_item(help_section)
54 |
55 | action = Gio.SimpleAction.new_stateful("night_mode", None, GLib.Variant.new_boolean(settings.get_is_night_mode()))
56 | action.connect("change-state", self.on_night_mode)
57 | self.add_action(action)
58 |
59 | action = Gio.SimpleAction.new("about", None)
60 | action.connect("activate", self.on_about)
61 | self.add_action(action)
62 |
63 | action = Gio.SimpleAction.new("quit", None)
64 | action.connect("activate", self.on_quit)
65 | self.add_action(action)
66 |
67 | if is_gnome() and not is_app_menu():
68 | self.set_app_menu(self.menu)
69 | logging.debug("Adding gnome shell menu")
70 |
71 | def do_activate(self, *args):
72 | if not self.win_object:
73 | self.win_object = Window(self)
74 | self.win_object.show_window()
75 | self.win = self.win_object.window
76 | self.add_window(self.win)
77 | else:
78 | self.win_object.show_window()
79 |
80 | def on_night_mode(self, action, gvariant):
81 | is_night_mode = not settings.get_is_night_mode()
82 | action.set_state(GLib.Variant.new_boolean(is_night_mode))
83 | settings.set_is_night_mode(is_night_mode)
84 | Gtk.Settings.get_default().set_property(
85 | "gtk-application-prefer-dark-theme", is_night_mode)
86 |
87 | @staticmethod
88 | def about_dialog():
89 | """
90 | Shows about dialog
91 | """
92 | builder = Gtk.Builder()
93 | builder.add_from_resource('/org/gnome/IconRequests/ui/about.ui')
94 |
95 | dialog = builder.get_object("AboutDialog")
96 | return dialog
97 |
98 | def __about_response(self, dialog, response_id):
99 | """
100 | Destroy about dialog when closed
101 | @param dialog as Gtk.Dialog
102 | @param response id as int
103 | """
104 | dialog.destroy()
105 |
106 | def on_about(self, *args):
107 | """
108 | Shows about dialog
109 | """
110 | dialog = Application.about_dialog()
111 | dialog.set_transient_for(self.win)
112 | dialog.run()
113 | dialog.destroy()
114 |
115 | def on_quit(self, *args):
116 | """
117 | Close the application, stops all threads
118 | and clear clipboard for safety reasons
119 | """
120 | if self.win:
121 | self.win_object.save_window_state()
122 | self.win.destroy()
123 | self.quit()
124 |
--------------------------------------------------------------------------------
/IconRequests/modules/desktop.py:
--------------------------------------------------------------------------------
1 | from IconRequests.const import repositories
2 | from xdg.DesktopEntry import DesktopEntry
3 | from os import path, listdir
4 | from json import loads
5 | from gi import require_version
6 | require_version("Gtk", "3.0")
7 | from gi.repository import Gtk, Gio, GLib
8 |
9 |
10 | class DesktopFile(DesktopEntry):
11 |
12 | def __init__(self, _file, upload_service, supported_icons, issues_list):
13 | if not path.exists(_file):
14 | raise DesktopFileNotFound
15 | DesktopEntry.__init__(self, filename=_file)
16 | self.issues_list = issues_list
17 | self.upload_service = upload_service
18 | self.desktop_file = path.basename(_file)
19 | self.supported_icons = supported_icons
20 | self.path = _file.replace(self.desktop_file, "")
21 | self.issue_url = None
22 | self.get_icon_informations()
23 |
24 | def get_is_supported(self):
25 | extensions = [".svg", ".png", ".xpm"]
26 | icon_name = path.basename(self.getIcon())
27 | for ext in extensions:
28 | icon_name = icon_name.replace(ext, "")
29 | return icon_name in self.supported_icons
30 |
31 | def get_icon_informations(self):
32 | theme = Gtk.IconTheme.get_default()
33 | self.is_hardcoded_icon()
34 | self.icon_path = ""
35 | icon_name = self.getIcon()
36 | self.is_supported = self.get_is_supported()
37 | self.supported_icons = None
38 | full_path = False
39 | if self.is_hardcoded:
40 | self.icon_path = icon_name
41 | if len(self.icon_path.split("/")) == 1:
42 | icon_name = path.splitext(self.getIcon())[0]
43 | else:
44 | self.icon_path = self.getIcon()
45 | full_path = True
46 |
47 | icon = theme.lookup_icon(icon_name, 48, 0)
48 | if icon and not full_path:
49 | self.icon_path = icon.get_filename()
50 | if self.is_hardcoded:
51 | self.is_supported = True
52 |
53 | if not self.icon_path or not path.exists(self.icon_path):
54 | icon = theme.lookup_icon(
55 | "image-missing", 48, 0)
56 | if icon:
57 | self.icon_path = icon.get_filename()
58 |
59 | def is_hardcoded_icon(self):
60 | img_exts = ["png", "svg", "xpm"]
61 | icon_path = self.getIcon().split("/")
62 | ext = path.splitext(self.getIcon())[1].strip(".")
63 | is_hardcoded = False
64 | if ext.lower() in img_exts or len(icon_path) > 1:
65 | is_hardcoded = True
66 | self.is_hardcoded = is_hardcoded
67 |
68 | def upload(self):
69 | """ Upload the missing icon to the current image service"""
70 | issue_url = None
71 | app_name = self.getName().lower()
72 | app_icon = self.getIcon()
73 | if self.issues_list and isinstance(self.issues_list[0], str):
74 | raise APIRateLimit
75 | else:
76 | for issue in self.issues_list:
77 | title = issue.get("title", "").lower()
78 | body = issue.get("body", "")
79 | if app_icon in body or app_name in title:
80 | issue_url = issue["html_url"]
81 | break
82 | if not issue_url:
83 | self.icon_url = self.upload_service.upload(self.icon_path, self.getName())
84 | return True
85 | else:
86 | Gio.app_info_launch_default_for_uri(issue_url)
87 | return False
88 |
89 | def report(self):
90 | theme = Gio.Settings.new(
91 | "org.gnome.desktop.interface").get_string("icon-theme")
92 | if repositories.is_supported(theme):
93 | issue_model_obj = Gio.File.new_for_uri(
94 | 'resource:///org/gnome/IconRequests/issue.model')
95 | issue_model = str(issue_model_obj.load_contents(None)[1].decode("utf-8"))
96 | repo_url = repositories.get_url(theme)
97 | issue_title = "Icon Request : %s" % self.getName()
98 | issue_model = issue_model.replace(
99 | "{app.name}", self.getName())
100 | issue_model = issue_model.replace(
101 | "{app.icon}", self.getIcon())
102 | issue_model = issue_model.replace(
103 | "{app.icon_url}", self.icon_url)
104 | issue_model = issue_model.replace(
105 | "{app.desktop}", self.desktop_file)
106 | issue_model = issue_model.replace("{theme}", theme)
107 | issue_model = issue_model.replace("\n", "%0A")
108 | url = "%s/issues/new?title=%s&body=%s" % (
109 | repo_url, issue_title, issue_model)
110 | Gio.app_info_launch_default_for_uri(url)
111 | else:
112 | raise ThemeNotSupported
113 |
114 |
115 | class APIRateLimit(Exception):
116 |
117 | def __init__(self):
118 | super(APIRateLimit, self).__init__()
119 |
120 |
121 | class ThemeNotSupported(Exception):
122 |
123 | def __init__(self):
124 | super(ThemeNotSupported, self).__init__()
125 |
126 |
127 | class DesktopFileNotFound(Exception):
128 |
129 | def __init__(self):
130 | super(DesktopFileNotFound, self).__init__()
131 |
132 |
133 | class DesktopFileCorrupted(Exception):
134 |
135 | def __init__(self):
136 | super(DesktopFileCorrupted, self).__init__()
137 |
138 |
139 | class DesktopFileInvalid(Exception):
140 |
141 | def __init__(self):
142 | super(DesktopFileInvalid, self).__init__()
143 |
--------------------------------------------------------------------------------
/IconRequests/utils.py:
--------------------------------------------------------------------------------
1 | from os import path, makedirs, remove
2 | from gi import require_version
3 | import requests
4 | import json
5 | from urllib.parse import urlencode
6 | from IconRequests.const import ISSUES_PER_PAGE, NB_PAGES
7 | from io import BytesIO
8 | from subprocess import Popen, PIPE, call
9 | from shutil import copyfile
10 | from glob import glob
11 | require_version("Gtk", "3.0")
12 | from gi.repository import Gtk, GdkPixbuf, Gio, GLib
13 | try:
14 | require_version('Rsvg', '2.0')
15 | from gi.repository import Rsvg
16 | import cairo
17 | use_inkscape = False
18 | except (ImportError, AttributeError, ValueError):
19 | ink_flag = call(['which', 'inkscape'], stdout=PIPE, stderr=PIPE)
20 | if ink_flag == 0:
21 | use_inkscape = True
22 | else:
23 | exit("Can't load cariosvg nor inkscape")
24 |
25 |
26 | def copy_file(src, destination, overwrite=False):
27 | """
28 | Simple copy file function with the possibility to overwrite the file.
29 | Args :
30 | src(str) : source file
31 | dest(str) : destination folder
32 | overwrite(bool) : True to overwrite the file False by default
33 | """
34 | if overwrite:
35 | if path.isfile(destination):
36 | remove(destination)
37 | copyfile(src, destination)
38 | else:
39 | if not path.isfile(destination):
40 | copyfile(src, destination)
41 |
42 |
43 | def change_icon_name(desktop_file, icon_name):
44 | config = RawConfigParser()
45 | config.read(desktop_file)
46 | config.set("Desktop Entry", "Icon", icon_name)
47 |
48 |
49 | def is_gnome():
50 | """
51 | Check if the current desktop env is gnome
52 | """
53 | return GLib.getenv("XDG_CURRENT_DESKTOP").lower() == "gnome"
54 |
55 |
56 | def is_app_menu():
57 | """
58 | Check if the top application menu is enabled or not.
59 | """
60 | default = True
61 | try:
62 | gsettings = Gio.Settings.new('org.gnome.settings-daemon.plugins.xsettings')
63 | overrides = gsettings.get_value('overrides')['Gtk/ShellShowsAppMenu']
64 | show_app_menu = not bool(GLib.Variant.new_int32(overrides))
65 | except Exception:
66 | show_app_menu = default
67 | return show_app_menu
68 |
69 |
70 | def get_supported_icons():
71 | theme_name = Gio.Settings.new(
72 | "org.gnome.desktop.interface").get_string("icon-theme")
73 | icons_paths = [
74 | '{0}/.icons/{1}/'.format(GLib.get_home_dir(), theme_name),
75 | '{0}/.local/share/icons/{1}/'.format(GLib.get_home_dir(), theme_name),
76 | '/usr/local/share/icons/{0}/'.format(theme_name),
77 | '/usr/share/icons/{0}/'.format(theme_name),
78 | ]
79 | subdirs = []
80 | icons_dirs = ["apps", "applications", "web"]
81 | size_dirs = ["48", "48x48", "scalable"]
82 | for size in size_dirs:
83 | for icon_dir in icons_dirs:
84 | subdirs.append("{0}/{1}/".format(size, icon_dir))
85 | subdirs.append("{0}/{1}/".format(icon_dir, size))
86 | icons = []
87 | folder_icons = []
88 | extensions = [".svg" , ".png", ".xpm"]
89 | for icon_path in icons_paths:
90 | for icon_size in subdirs:
91 | icons_dir = icon_path + icon_size
92 | if path.exists(icons_dir):
93 | for ext in extensions:
94 | folder_icons.extend(glob("{0}*{1}".format(icons_dir, ext)))
95 | for icon in folder_icons:
96 | icon = path.basename(icon)
97 | for ext in extensions:
98 | icon = icon.replace(ext, "")
99 | if icon not in icons:
100 | icons.append(icon)
101 | folder_icons = []
102 | return icons
103 |
104 | def convert_svg2png(infile, outfile, w, h):
105 | """
106 | Converts svg files to png using Cairosvg or Inkscape
107 | @file_path : String; the svg file absolute path
108 | @dest_path : String; the png file absolute path
109 | """
110 | if use_inkscape:
111 | p = Popen(["inkscape", "-z", "-f", infile, "-e", outfile,
112 | "-w", str(w), "-h", str(h)],
113 | stdout=PIPE, stderr=PIPE)
114 | output, err = p.communicate()
115 | else:
116 | handle = Rsvg.Handle()
117 | svg = handle.new_from_file(infile)
118 | dim = svg.get_dimensions()
119 |
120 | img = cairo.ImageSurface(cairo.FORMAT_ARGB32, w, h)
121 | ctx = cairo.Context(img)
122 | ctx.scale(w / dim.width, h / dim.height)
123 | svg.render_cairo(ctx)
124 |
125 | png_io = BytesIO()
126 | img.write_to_png(png_io)
127 | with open(outfile, 'wb') as fout:
128 | fout.write(png_io.getvalue())
129 | svg.close()
130 | png_io.close()
131 | img.finish()
132 |
133 |
134 | def get_icon(icon_path):
135 | """
136 | Generate a GdkPixbuf image
137 | :param image: icon name or image path
138 | :return: GdkPixbux Image
139 | """
140 | try:
141 | if "symbolic" in icon_path:
142 | icon = (None, icon_path)
143 | else:
144 | icon = GdkPixbuf.Pixbuf.new_from_file(icon_path)
145 | if icon.get_width() != 48 or icon.get_height() != 48:
146 | icon = icon.scale_simple(48, 48, GdkPixbuf.InterpType.BILINEAR)
147 | return icon
148 | except GLib.Error:
149 | return None
150 |
151 | def get_issues_list(repository):
152 | """
153 | Get a list of open issues on a repository.
154 | """
155 | cache_dir = path.join(GLib.get_user_cache_dir(), "IconRequests", "")
156 | cache_file = path.join(cache_dir, "{0}.json".format(repository.replace("/", "-")))
157 | if not path.exists(cache_dir):
158 | makedirs(cache_dir)
159 | issues_list = []
160 | url_data = {
161 | "state" : "open",
162 | "per_page": str(ISSUES_PER_PAGE),
163 | "page" : "1"
164 | }
165 | base_uri = "https://api.github.com/repos/{0}/issues?".format(repository)
166 | for page in range(1, NB_PAGES + 1):
167 | url_data["page"] = str(page)
168 | try:
169 | query = requests.get(base_uri + urlencode(url_data))
170 | issues_list.extend(query.json())
171 | except requests.exceptions.ConnectionError:
172 | issues_list = []
173 | break
174 | if len(issues_list) != 0 and not isinstance(issues_list[0], str):
175 | if path.exists(cache_file):
176 | remove(cache_file)
177 | with open(cache_file, 'w') as file_object:
178 | json.dump(issues_list, file_object, sort_keys=True, indent=4)
179 | file_object.close()
180 | else:
181 | if path.exists(cache_file):
182 | with open(cache_file, 'r') as file_object:
183 | issues_list = json.load(file_object)
184 | file_object.close()
185 | return issues_list
186 |
--------------------------------------------------------------------------------
/IconRequests/widgets/application_row.py:
--------------------------------------------------------------------------------
1 | from gi import require_version
2 | require_version("Gtk", "3.0")
3 | from gi.repository import Gtk, GObject, GdkPixbuf, Gio, GLib, Gdk, Pango
4 | from gettext import gettext as _
5 | import logging
6 | from os import path
7 | from IconRequests.modules.upload.upload import ConnexionError
8 | from IconRequests.modules.desktop import ThemeNotSupported, APIRateLimit
9 | from IconRequests.utils import get_icon, change_icon_name, copy_file
10 | from threading import Thread
11 |
12 |
13 | class ApplicationRow(Gtk.ListBoxRow, GObject.GObject):
14 | stack_name = None
15 | __gsignals__ = {
16 | 'icon_uploaded': (GObject.SignalFlags.RUN_LAST, None, (bool,))
17 | }
18 |
19 | def __init__(self, desktop_file, notification):
20 | GObject.GObject.__init__(self)
21 | Gtk.ListBoxRow.__init__(self)
22 | self.desktop_file = desktop_file
23 | self.notification = notification
24 | self.spinner = Gtk.Spinner()
25 | self.generate()
26 |
27 | def generate(self):
28 | main_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
29 | actions_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
30 | image_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
31 | info_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
32 |
33 | image = Gtk.Image(xalign=0)
34 | icon = get_icon(self.desktop_file.icon_path)
35 | if icon:
36 | if type(icon) == tuple:
37 | icon_name = path.basename(icon[1]).replace(".svg", "")
38 | image.set_from_gicon(Gio.ThemedIcon(name=icon_name),
39 | Gtk.IconSize.DIALOG)
40 | else:
41 | image.set_from_pixbuf(icon)
42 | else:
43 | image.set_from_icon_name("image-missing", Gtk.IconSize.DIALOG)
44 | image_box.pack_start(image, True, False, 6)
45 |
46 | name_label = Gtk.Label()
47 | name_label.set_ellipsize(Pango.EllipsizeMode.END)
48 | name_label.set_justify(Gtk.Justification.LEFT)
49 | name_label.set_text(self.desktop_file.getName())
50 | name_label.get_style_context().add_class("application-name")
51 | name_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
52 | name_box.add(name_label)
53 | info_box.pack_start(name_box, False, False, 3)
54 |
55 | description_label = Gtk.Label()
56 | description_label.set_justify(Gtk.Justification.LEFT)
57 | description_label.set_text(self.desktop_file.getComment())
58 | description_label.set_tooltip_text(self.desktop_file.getComment())
59 | description_label.set_ellipsize(Pango.EllipsizeMode.END)
60 | description_label.get_style_context().add_class("application-label")
61 | label_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
62 | label_box.add(description_label)
63 | info_box.pack_start(label_box, False, False, 3)
64 |
65 | path_label = Gtk.Label()
66 | path_label.set_justify(Gtk.Justification.LEFT)
67 | file_path = path.join(self.desktop_file.path, self.desktop_file.desktop_file)
68 | path_label.set_text(file_path)
69 | path_label.set_tooltip_text(file_path)
70 | path_label.set_ellipsize(Pango.EllipsizeMode.END)
71 | path_label.get_style_context().add_class("application-path")
72 | path_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
73 | path_box.add(path_label)
74 | info_box.pack_start(path_box, False, False, 3)
75 |
76 | if not self.desktop_file.is_supported:
77 | self.spinner = Gtk.Spinner()
78 | self.spinner.set_visible(False)
79 | self.spinner.set_no_show_all(True)
80 | self.report_label = Gtk.Label()
81 | self.report_label.set_text(_("Report"))
82 | self.report_button = Gtk.Button()
83 | self.report_button.add(self.report_label)
84 | self.report_button.get_style_context().add_class("text-button")
85 | self.report_button.connect("clicked", self.report_missing_icon)
86 | report_box = Gtk.Box(
87 | orientation=Gtk.Orientation.VERTICAL, valign=Gtk.Align.CENTER)
88 | report_box.add(self.report_button)
89 | main_box.pack_end(report_box, False, False, 6)
90 |
91 | if self.desktop_file.is_hardcoded:
92 | self.fix_button = Gtk.Button()
93 | self.fix_label = Gtk.Label()
94 | self.fix_label.set_text(_("Fix"))
95 | self.fix_button.add(self.fix_label)
96 | self.fix_button.set_sensitive(False)
97 | self.fix_button.get_style_context().add_class("text-button")
98 | self.fix_button.set_tooltip_text("Not supported at the moment")
99 | self.fix_button.connect("clicked", self.fix_hardcoded_icon)
100 | fix_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL,
101 | valign=Gtk.Align.CENTER)
102 | fix_box.add(self.fix_button)
103 | main_box.pack_end(fix_box, False, False, 6)
104 | if not self.desktop_file.is_supported:
105 | self.report_button.set_sensitive(False)
106 |
107 | main_box.pack_start(image_box, False, False, 6)
108 | main_box.pack_start(info_box, False, False, 6)
109 | self.add(main_box)
110 |
111 | def fix_hardcoded_icon(self, *args):
112 | icon_name = self.desktop_file.getIcon()
113 | icon_extension = path.splitext(icon_name)[1].lower().strip(".")
114 | new_icon_name = icon_name.split(
115 | "/")[-1].replace(".%s" % icon_extension, "")
116 | if new_icon_name.lower() in ["logo", "icon"]:
117 | new_icon_name = self.desktop_file.getName().lower()
118 | if icon_extension == "png":
119 | icon_path = "/usr/share/icons/hicolor/48x48/"
120 | elif icon_extension == "svg":
121 | icon_path = "/usr/share/icons/hicolor/scalable/"
122 | elif icon_extension == "xpm":
123 | icon_path = "/usr/share/pixmaps/"
124 | copy_file(icon_name, icon_path +
125 | new_icon_name + "." + icon_extension)
126 | change_icon_name(self.desktop_file.path +
127 | self.desktop_file.desktop, new_icon_name)
128 |
129 | def emit(self, *args):
130 | GLib.idle_add(GObject.GObject.emit, self, *args)
131 |
132 | def run(self):
133 | upload_status = False
134 | try:
135 | upload_status = self.desktop_file.upload()
136 | except ConnexionError:
137 | self.notification.set_message(_("Please check your connexion"))
138 | self.notification.set_type(Gtk.MessageType.ERROR)
139 | self.notification.show()
140 | except ThemeNotSupported:
141 | self.notification.set_message(_("Theme not supported"))
142 | self.notification.set_type(Gtk.MessageType.ERROR)
143 | self.notification.show()
144 | except APIRateLimit:
145 | self.notification.set_message(_("You've reached your API limits"))
146 | self.notification.set_type(Gtk.MessageType.INFO)
147 | self.notification.show()
148 | self.emit("icon_uploaded", upload_status)
149 |
150 | def report_missing_icon(self, *args):
151 | self.report_button.set_sensitive(False)
152 | self.report_button.get_style_context().remove_class("text-button")
153 | self.spinner.set_visible(True)
154 | self.spinner.set_no_show_all(False)
155 | self.spinner.start()
156 | self.report_button.remove(self.report_label)
157 | self.report_button.add(self.spinner)
158 | self.thread = Thread(target=self.run)
159 | self.thread.daemon = True
160 | self.thread.start()
161 |
162 | def do_icon_uploaded(self, signal):
163 | if signal:
164 | try:
165 | self.desktop_file.report()
166 | except ThemeNotSupported:
167 | self.notification.set_message(_("Theme not supported"))
168 | self.notification.set_type(Gtk.MessageType.INFO)
169 | self.notification.show()
170 | self.report_button.remove(self.spinner)
171 | self.report_button.add(self.report_label)
172 | self.spinner.set_visible(False)
173 | self.spinner.set_no_show_all(True)
174 | self.spinner.stop()
175 | self.report_button.get_style_context().add_class("text-button")
176 | self.report_button.set_sensitive(True)
177 |
--------------------------------------------------------------------------------
/IconRequests/widgets/window.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from gettext import gettext as _
3 | import logging
4 | from IconRequests.const import DESKTOP_FILE_DIRS, settings, repositories, ICONS_IGNORE_LIST
5 | from IconRequests.utils import (get_supported_icons, is_gnome,
6 | is_app_menu, get_issues_list)
7 | from IconRequests.modules.upload.imgur import Imgur
8 | from IconRequests.modules.desktop import DesktopFile
9 | from IconRequests.widgets.notification import Notification
10 | from IconRequests.widgets.application_row import ApplicationRow
11 | from threading import Thread
12 | from os import path
13 | from glob import glob
14 | from gi import require_version
15 | require_version("Gtk", "3.0")
16 | from gi.repository import Gtk, Gio, Gdk, GObject, GLib
17 |
18 |
19 |
20 | class Window(Gtk.ApplicationWindow, GObject.GObject):
21 | __gsignals__ = {
22 | 'loaded': (GObject.SIGNAL_RUN_FIRST, None, (bool,))
23 | }
24 |
25 | def __init__(self, application):
26 | GObject.GObject.__init__(self)
27 | # Initiate the uploading server
28 | self.upload_service = Imgur(settings)
29 | self.builder = Gtk.Builder.new_from_resource(
30 | "/org/gnome/IconRequests/ui/mainwindow.ui")
31 | self.generate_window(application)
32 |
33 | def generate_window(self, application):
34 | self.window = self.builder.get_object("MainWindow")
35 | self.window.connect("key-press-event", self.__on_key_press)
36 |
37 | notification = self.builder.get_object("Notification")
38 | notification_msg = self.builder.get_object("NotificationMessage")
39 |
40 | self.notification = Notification(notification, notification_msg)
41 |
42 | position_x, position_y = settings.get_window_position()
43 | if position_x and position_y:
44 | self.window.move(position_x, position_y)
45 | else:
46 | self.window.set_position(Gtk.WindowPosition.CENTER_ALWAYS)
47 | self.stack = self.builder.get_object("Stack")
48 | self.main_stack = self.builder.get_object("MainStack")
49 | # Listbox
50 | self.all = self.builder.get_object("AllListBox")
51 | self.unsupported = self.builder.get_object("unsupportedListBox")
52 | self.hardcoded = self.builder.get_object("hardcodedListBox")
53 |
54 | self.window.set_application(application)
55 | self.window.connect("delete-event", lambda x, y: application.on_quit())
56 |
57 | self.search_button = self.builder.get_object("SearchButton")
58 | self.search_button.connect("toggled", self.__toggle_search)
59 |
60 | self.search_entry = self.builder.get_object("searchEntry")
61 | self.search_entry.set_width_chars(28)
62 | self.search_entry.connect("search-changed", self.__filter_applications)
63 |
64 | self.menu_button = self.builder.get_object("PopoverMenuButton")
65 | if not is_gnome() or is_app_menu():
66 | self.menu_button.set_visible(True)
67 | self.popover = Gtk.Popover.new_from_model(self.menu_button, application.menu)
68 | self.popover.props.width_request = 200
69 | self.menu_button.connect("clicked", self.show_menu_popover)
70 | else:
71 | self.menu_button.set_visible(False)
72 | self.menu_button.set_no_show_all(True)
73 | self.revealer = self.builder.get_object("Revealer")
74 | self.search_list = [self.all, self.unsupported, self.hardcoded]
75 |
76 | self.main_stack.set_visible_child_name("loading")
77 | # Watch the icon name gsettings
78 | self.gsettings = Gio.Settings.new("org.gnome.desktop.interface")
79 | self.gsettings.connect("changed", self.refresh_icons_view)
80 |
81 | self.start()
82 |
83 | def emit(self, *args):
84 | GLib.idle_add(GObject.GObject.emit, self, *args)
85 |
86 | def refresh_icons_view(self, gsettings, key):
87 | if key == "icon-theme":
88 | self.main_stack.set_visible_child_name("loading")
89 | for listbox in self.search_list:
90 | for child in listbox.get_children():
91 | listbox.remove(child)
92 | self.search_button.set_active(False)
93 | self.search_entry.set_text("")
94 | self.start()
95 |
96 | def start(self):
97 | thread = Thread(target=self.generate_apps_list)
98 | thread.daemon = True
99 | thread.start()
100 |
101 | def __on_key_press(self, widget, event):
102 | keyname = Gdk.keyval_name(event.keyval).lower()
103 | is_ready = self.main_stack.get_visible_child_name() != "loading"
104 | if keyname == 'escape' and self.search_button.get_active() and is_ready:
105 | if self.search_entry.is_focus():
106 | self.search_button.set_active(False)
107 | self.search_entry.set_text("")
108 | else:
109 | self.search_entry.grab_focus_without_selecting()
110 | return True
111 |
112 | if keyname == "backspace":
113 | if (len(self.search_entry.get_text()) == 0
114 | and self.revealer.get_reveal_child()):
115 | self.search_button.set_active(False)
116 | return True
117 |
118 | if event.state & Gdk.ModifierType.CONTROL_MASK:
119 | if keyname == 'f':
120 | self.search_button.set_active(
121 | not self.search_button.get_active())
122 | return True
123 | return False
124 |
125 | def __toggle_search(self, *args):
126 | if self.revealer.get_reveal_child():
127 | self.revealer.set_reveal_child(False)
128 | self.search_entry.set_text("")
129 | for search_list in self.search_list:
130 | search_list.set_filter_func(lambda x, y, z: True, None, False)
131 | self.main_stack.set_visible_child_name("applications")
132 | else:
133 | self.revealer.set_reveal_child(True)
134 | self.search_entry.grab_focus_without_selecting()
135 |
136 | def filter_func(self, row, data, notify_destroy):
137 | """
138 | Filter function, used to check if the entered data exists on the application ListBox
139 | """
140 | app_label = row.desktop_file.getName()
141 | data = data.lower()
142 | if len(data) > 0:
143 | return data in app_label.lower()
144 | else:
145 | self.main_stack.set_visible_child_name("applications")
146 | return True
147 |
148 | def __filter_applications(self, entry):
149 | data = entry.get_text().strip()
150 | for search_list in self.search_list:
151 | stack = search_list.get_parent().get_parent().get_parent()
152 | search_list.set_filter_func(self.filter_func, data, True)
153 |
154 | def show_menu_popover(self, *args):
155 | if self.popover:
156 | if self.popover.get_visible():
157 | self.popover.hide()
158 | else:
159 | self.popover.show_all()
160 |
161 | def show_window(self):
162 | self.window.show_all()
163 | self.window.present()
164 |
165 | def generate_apps_list(self):
166 | self.builder.get_object("loadingSpinner").start()
167 | supported_icons = get_supported_icons()
168 | theme = Gio.Settings.new("org.gnome.desktop.interface").get_string("icon-theme")
169 | repo = None
170 | try:
171 | repo = repositories.get_repo(theme)
172 | except KeyError:
173 | pass
174 | issues_list = []
175 | if repo:
176 | issues_list = get_issues_list(repo)
177 | self.db = []
178 | already_added = []
179 | for desktop_dir in DESKTOP_FILE_DIRS:
180 | if path.isdir(desktop_dir):
181 | all_files = glob("{0}*.desktop".format(desktop_dir))
182 | for desktop_file in all_files:
183 | obj = DesktopFile(desktop_file, self.upload_service,
184 | supported_icons, issues_list)
185 | icon_name = obj.getIcon()
186 | if icon_name not in already_added and icon_name not in ICONS_IGNORE_LIST:
187 | self.db.append(obj)
188 | already_added.append(icon_name)
189 | self.db = sorted(self.db, key=lambda x: x.getName().lower())
190 | self.emit("loaded", True)
191 | return False
192 |
193 | def do_loaded(self, signal):
194 | if signal:
195 | for desktop_file in self.db:
196 | row = ApplicationRow(desktop_file, self.notification)
197 | if desktop_file.is_hardcoded:
198 | self.hardcoded.add(row)
199 | elif not desktop_file.is_supported:
200 | self.unsupported.add(row)
201 | else:
202 | self.all.add(row)
203 | self.builder.get_object("loadingSpinner").stop()
204 | self.main_stack.set_visible_child_name("applications")
205 | self.all.show_all()
206 | self.hardcoded.show_all()
207 | self.unsupported.show_all()
208 |
209 | def save_window_state(self):
210 | settings.set_window_postion(self.window.get_position())
211 |
--------------------------------------------------------------------------------
/data/ui/mainwindow.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | True
7 | False
8 | search-symbolic
9 |
10 |
11 |
12 | True
13 | False
14 | open-menu-symbolic
15 |
16 |
17 | 400
18 | 650
19 | False
20 | False
21 | 400
22 | 650
23 | toplevel
24 | icon-requests
25 |
26 |
27 | True
28 | False
29 |
30 |
31 | True
32 | False
33 | True
34 | True
35 |
36 |
37 | True
38 | False
39 | center
40 | center
41 | 18
42 |
43 |
44 | True
45 | False
46 | Loading desktop file informations...
47 |
48 |
49 | 0
50 | 1
51 |
52 |
53 |
54 |
55 | True
56 | False
57 |
58 |
59 | 0
60 | 0
61 |
62 |
63 |
64 |
65 | loading
66 | page0
67 |
68 |
69 |
70 |
71 | True
72 | False
73 | 500
74 | crossfade
75 |
76 |
77 | True
78 | True
79 | in
80 |
81 |
82 | True
83 | False
84 | none
85 |
86 |
87 |
88 |
89 | all
90 | All
91 |
92 |
93 |
94 |
95 | True
96 | True
97 | in
98 |
99 |
100 | True
101 | False
102 | none
103 |
104 |
105 |
106 |
107 | unsupported
108 | Unsupported
109 | 1
110 |
111 |
112 |
113 |
114 | True
115 | True
116 | in
117 |
118 |
119 | True
120 | False
121 | none
122 |
123 |
124 |
125 |
126 | hardcoded
127 | Hardcoded
128 | 2
129 |
130 |
131 |
132 |
133 | applications
134 | page1
135 | 1
136 |
137 |
138 |
139 |
140 |
141 | 0
142 | 2
143 |
144 |
145 |
146 |
147 | True
148 | False
149 | False
150 |
151 |
152 | True
153 | False
154 | False
155 | True
156 |
157 |
158 | False
159 | 16
160 |
161 |
162 | True
163 | False
164 |
165 |
166 | False
167 | True
168 | 0
169 |
170 |
171 |
172 |
173 | False
174 | False
175 | 0
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 | 0
184 | 1
185 |
186 |
187 |
188 |
189 | True
190 | False
191 | False
192 |
193 |
194 | True
195 | False
196 | True
197 | False
198 |
199 |
200 | True
201 | True
202 | edit-find-symbolic
203 | False
204 | False
205 |
206 |
207 |
208 |
209 |
210 |
211 | 0
212 | 0
213 |
214 |
215 |
216 |
217 |
218 |
219 | True
220 | False
221 | True
222 |
223 |
224 | True
225 | False
226 | Stack
227 |
228 |
229 |
230 |
231 | True
232 | True
233 | True
234 | image1
235 |
236 |
237 | end
238 |
239 |
240 |
241 |
242 | False
243 | True
244 | image2
245 |
246 |
247 | end
248 |
249 |
250 |
251 |
252 |
253 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU GENERAL PUBLIC LICENSE
2 | Version 3, 29 June 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 | Preamble
9 |
10 | The GNU General Public License is a free, copyleft license for
11 | software and other kinds of works.
12 |
13 | The licenses for most software and other practical works are designed
14 | to take away your freedom to share and change the works. By contrast,
15 | the GNU General Public License is intended to guarantee your freedom to
16 | share and change all versions of a program--to make sure it remains free
17 | software for all its users. We, the Free Software Foundation, use the
18 | GNU General Public License for most of our software; it applies also to
19 | any other work released this way by its authors. You can apply it to
20 | your programs, too.
21 |
22 | When we speak of free software, we are referring to freedom, not
23 | price. Our General Public Licenses are designed to make sure that you
24 | have the freedom to distribute copies of free software (and charge for
25 | them if you wish), that you receive source code or can get it if you
26 | want it, that you can change the software or use pieces of it in new
27 | free programs, and that you know you can do these things.
28 |
29 | To protect your rights, we need to prevent others from denying you
30 | these rights or asking you to surrender the rights. Therefore, you have
31 | certain responsibilities if you distribute copies of the software, or if
32 | you modify it: responsibilities to respect the freedom of others.
33 |
34 | For example, if you distribute copies of such a program, whether
35 | gratis or for a fee, you must pass on to the recipients the same
36 | freedoms that you received. You must make sure that they, too, receive
37 | or can get the source code. And you must show them these terms so they
38 | know their rights.
39 |
40 | Developers that use the GNU GPL protect your rights with two steps:
41 | (1) assert copyright on the software, and (2) offer you this License
42 | giving you legal permission to copy, distribute and/or modify it.
43 |
44 | For the developers' and authors' protection, the GPL clearly explains
45 | that there is no warranty for this free software. For both users' and
46 | authors' sake, the GPL requires that modified versions be marked as
47 | changed, so that their problems will not be attributed erroneously to
48 | authors of previous versions.
49 |
50 | Some devices are designed to deny users access to install or run
51 | modified versions of the software inside them, although the manufacturer
52 | can do so. This is fundamentally incompatible with the aim of
53 | protecting users' freedom to change the software. The systematic
54 | pattern of such abuse occurs in the area of products for individuals to
55 | use, which is precisely where it is most unacceptable. Therefore, we
56 | have designed this version of the GPL to prohibit the practice for those
57 | products. If such problems arise substantially in other domains, we
58 | stand ready to extend this provision to those domains in future versions
59 | of the GPL, as needed to protect the freedom of users.
60 |
61 | Finally, every program is threatened constantly by software patents.
62 | States should not allow patents to restrict development and use of
63 | software on general-purpose computers, but in those that do, we wish to
64 | avoid the special danger that patents applied to a free program could
65 | make it effectively proprietary. To prevent this, the GPL assures that
66 | patents cannot be used to render the program non-free.
67 |
68 | The precise terms and conditions for copying, distribution and
69 | modification follow.
70 |
71 | TERMS AND CONDITIONS
72 |
73 | 0. Definitions.
74 |
75 | "This License" refers to version 3 of the GNU General Public License.
76 |
77 | "Copyright" also means copyright-like laws that apply to other kinds of
78 | works, such as semiconductor masks.
79 |
80 | "The Program" refers to any copyrightable work licensed under this
81 | License. Each licensee is addressed as "you". "Licensees" and
82 | "recipients" may be individuals or organizations.
83 |
84 | To "modify" a work means to copy from or adapt all or part of the work
85 | in a fashion requiring copyright permission, other than the making of an
86 | exact copy. The resulting work is called a "modified version" of the
87 | earlier work or a work "based on" the earlier work.
88 |
89 | A "covered work" means either the unmodified Program or a work based
90 | on the Program.
91 |
92 | To "propagate" a work means to do anything with it that, without
93 | permission, would make you directly or secondarily liable for
94 | infringement under applicable copyright law, except executing it on a
95 | computer or modifying a private copy. Propagation includes copying,
96 | distribution (with or without modification), making available to the
97 | public, and in some countries other activities as well.
98 |
99 | To "convey" a work means any kind of propagation that enables other
100 | parties to make or receive copies. Mere interaction with a user through
101 | a computer network, with no transfer of a copy, is not conveying.
102 |
103 | An interactive user interface displays "Appropriate Legal Notices"
104 | to the extent that it includes a convenient and prominently visible
105 | feature that (1) displays an appropriate copyright notice, and (2)
106 | tells the user that there is no warranty for the work (except to the
107 | extent that warranties are provided), that licensees may convey the
108 | work under this License, and how to view a copy of this License. If
109 | the interface presents a list of user commands or options, such as a
110 | menu, a prominent item in the list meets this criterion.
111 |
112 | 1. Source Code.
113 |
114 | The "source code" for a work means the preferred form of the work
115 | for making modifications to it. "Object code" means any non-source
116 | form of a work.
117 |
118 | A "Standard Interface" means an interface that either is an official
119 | standard defined by a recognized standards body, or, in the case of
120 | interfaces specified for a particular programming language, one that
121 | is widely used among developers working in that language.
122 |
123 | The "System Libraries" of an executable work include anything, other
124 | than the work as a whole, that (a) is included in the normal form of
125 | packaging a Major Component, but which is not part of that Major
126 | Component, and (b) serves only to enable use of the work with that
127 | Major Component, or to implement a Standard Interface for which an
128 | implementation is available to the public in source code form. A
129 | "Major Component", in this context, means a major essential component
130 | (kernel, window system, and so on) of the specific operating system
131 | (if any) on which the executable work runs, or a compiler used to
132 | produce the work, or an object code interpreter used to run it.
133 |
134 | The "Corresponding Source" for a work in object code form means all
135 | the source code needed to generate, install, and (for an executable
136 | work) run the object code and to modify the work, including scripts to
137 | control those activities. However, it does not include the work's
138 | System Libraries, or general-purpose tools or generally available free
139 | programs which are used unmodified in performing those activities but
140 | which are not part of the work. For example, Corresponding Source
141 | includes interface definition files associated with source files for
142 | the work, and the source code for shared libraries and dynamically
143 | linked subprograms that the work is specifically designed to require,
144 | such as by intimate data communication or control flow between those
145 | subprograms and other parts of the work.
146 |
147 | The Corresponding Source need not include anything that users
148 | can regenerate automatically from other parts of the Corresponding
149 | Source.
150 |
151 | The Corresponding Source for a work in source code form is that
152 | same work.
153 |
154 | 2. Basic Permissions.
155 |
156 | All rights granted under this License are granted for the term of
157 | copyright on the Program, and are irrevocable provided the stated
158 | conditions are met. This License explicitly affirms your unlimited
159 | permission to run the unmodified Program. The output from running a
160 | covered work is covered by this License only if the output, given its
161 | content, constitutes a covered work. This License acknowledges your
162 | rights of fair use or other equivalent, as provided by copyright law.
163 |
164 | You may make, run and propagate covered works that you do not
165 | convey, without conditions so long as your license otherwise remains
166 | in force. You may convey covered works to others for the sole purpose
167 | of having them make modifications exclusively for you, or provide you
168 | with facilities for running those works, provided that you comply with
169 | the terms of this License in conveying all material for which you do
170 | not control copyright. Those thus making or running the covered works
171 | for you must do so exclusively on your behalf, under your direction
172 | and control, on terms that prohibit them from making any copies of
173 | your copyrighted material outside their relationship with you.
174 |
175 | Conveying under any other circumstances is permitted solely under
176 | the conditions stated below. Sublicensing is not allowed; section 10
177 | makes it unnecessary.
178 |
179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
180 |
181 | No covered work shall be deemed part of an effective technological
182 | measure under any applicable law fulfilling obligations under article
183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or
184 | similar laws prohibiting or restricting circumvention of such
185 | measures.
186 |
187 | When you convey a covered work, you waive any legal power to forbid
188 | circumvention of technological measures to the extent such circumvention
189 | is effected by exercising rights under this License with respect to
190 | the covered work, and you disclaim any intention to limit operation or
191 | modification of the work as a means of enforcing, against the work's
192 | users, your or third parties' legal rights to forbid circumvention of
193 | technological measures.
194 |
195 | 4. Conveying Verbatim Copies.
196 |
197 | You may convey verbatim copies of the Program's source code as you
198 | receive it, in any medium, provided that you conspicuously and
199 | appropriately publish on each copy an appropriate copyright notice;
200 | keep intact all notices stating that this License and any
201 | non-permissive terms added in accord with section 7 apply to the code;
202 | keep intact all notices of the absence of any warranty; and give all
203 | recipients a copy of this License along with the Program.
204 |
205 | You may charge any price or no price for each copy that you convey,
206 | and you may offer support or warranty protection for a fee.
207 |
208 | 5. Conveying Modified Source Versions.
209 |
210 | You may convey a work based on the Program, or the modifications to
211 | produce it from the Program, in the form of source code under the
212 | terms of section 4, provided that you also meet all of these conditions:
213 |
214 | a) The work must carry prominent notices stating that you modified
215 | it, and giving a relevant date.
216 |
217 | b) The work must carry prominent notices stating that it is
218 | released under this License and any conditions added under section
219 | 7. This requirement modifies the requirement in section 4 to
220 | "keep intact all notices".
221 |
222 | c) You must license the entire work, as a whole, under this
223 | License to anyone who comes into possession of a copy. This
224 | License will therefore apply, along with any applicable section 7
225 | additional terms, to the whole of the work, and all its parts,
226 | regardless of how they are packaged. This License gives no
227 | permission to license the work in any other way, but it does not
228 | invalidate such permission if you have separately received it.
229 |
230 | d) If the work has interactive user interfaces, each must display
231 | Appropriate Legal Notices; however, if the Program has interactive
232 | interfaces that do not display Appropriate Legal Notices, your
233 | work need not make them do so.
234 |
235 | A compilation of a covered work with other separate and independent
236 | works, which are not by their nature extensions of the covered work,
237 | and which are not combined with it such as to form a larger program,
238 | in or on a volume of a storage or distribution medium, is called an
239 | "aggregate" if the compilation and its resulting copyright are not
240 | used to limit the access or legal rights of the compilation's users
241 | beyond what the individual works permit. Inclusion of a covered work
242 | in an aggregate does not cause this License to apply to the other
243 | parts of the aggregate.
244 |
245 | 6. Conveying Non-Source Forms.
246 |
247 | You may convey a covered work in object code form under the terms
248 | of sections 4 and 5, provided that you also convey the
249 | machine-readable Corresponding Source under the terms of this License,
250 | in one of these ways:
251 |
252 | a) Convey the object code in, or embodied in, a physical product
253 | (including a physical distribution medium), accompanied by the
254 | Corresponding Source fixed on a durable physical medium
255 | customarily used for software interchange.
256 |
257 | b) Convey the object code in, or embodied in, a physical product
258 | (including a physical distribution medium), accompanied by a
259 | written offer, valid for at least three years and valid for as
260 | long as you offer spare parts or customer support for that product
261 | model, to give anyone who possesses the object code either (1) a
262 | copy of the Corresponding Source for all the software in the
263 | product that is covered by this License, on a durable physical
264 | medium customarily used for software interchange, for a price no
265 | more than your reasonable cost of physically performing this
266 | conveying of source, or (2) access to copy the
267 | Corresponding Source from a network server at no charge.
268 |
269 | c) Convey individual copies of the object code with a copy of the
270 | written offer to provide the Corresponding Source. This
271 | alternative is allowed only occasionally and noncommercially, and
272 | only if you received the object code with such an offer, in accord
273 | with subsection 6b.
274 |
275 | d) Convey the object code by offering access from a designated
276 | place (gratis or for a charge), and offer equivalent access to the
277 | Corresponding Source in the same way through the same place at no
278 | further charge. You need not require recipients to copy the
279 | Corresponding Source along with the object code. If the place to
280 | copy the object code is a network server, the Corresponding Source
281 | may be on a different server (operated by you or a third party)
282 | that supports equivalent copying facilities, provided you maintain
283 | clear directions next to the object code saying where to find the
284 | Corresponding Source. Regardless of what server hosts the
285 | Corresponding Source, you remain obligated to ensure that it is
286 | available for as long as needed to satisfy these requirements.
287 |
288 | e) Convey the object code using peer-to-peer transmission, provided
289 | you inform other peers where the object code and Corresponding
290 | Source of the work are being offered to the general public at no
291 | charge under subsection 6d.
292 |
293 | A separable portion of the object code, whose source code is excluded
294 | from the Corresponding Source as a System Library, need not be
295 | included in conveying the object code work.
296 |
297 | A "User Product" is either (1) a "consumer product", which means any
298 | tangible personal property which is normally used for personal, family,
299 | or household purposes, or (2) anything designed or sold for incorporation
300 | into a dwelling. In determining whether a product is a consumer product,
301 | doubtful cases shall be resolved in favor of coverage. For a particular
302 | product received by a particular user, "normally used" refers to a
303 | typical or common use of that class of product, regardless of the status
304 | of the particular user or of the way in which the particular user
305 | actually uses, or expects or is expected to use, the product. A product
306 | is a consumer product regardless of whether the product has substantial
307 | commercial, industrial or non-consumer uses, unless such uses represent
308 | the only significant mode of use of the product.
309 |
310 | "Installation Information" for a User Product means any methods,
311 | procedures, authorization keys, or other information required to install
312 | and execute modified versions of a covered work in that User Product from
313 | a modified version of its Corresponding Source. The information must
314 | suffice to ensure that the continued functioning of the modified object
315 | code is in no case prevented or interfered with solely because
316 | modification has been made.
317 |
318 | If you convey an object code work under this section in, or with, or
319 | specifically for use in, a User Product, and the conveying occurs as
320 | part of a transaction in which the right of possession and use of the
321 | User Product is transferred to the recipient in perpetuity or for a
322 | fixed term (regardless of how the transaction is characterized), the
323 | Corresponding Source conveyed under this section must be accompanied
324 | by the Installation Information. But this requirement does not apply
325 | if neither you nor any third party retains the ability to install
326 | modified object code on the User Product (for example, the work has
327 | been installed in ROM).
328 |
329 | The requirement to provide Installation Information does not include a
330 | requirement to continue to provide support service, warranty, or updates
331 | for a work that has been modified or installed by the recipient, or for
332 | the User Product in which it has been modified or installed. Access to a
333 | network may be denied when the modification itself materially and
334 | adversely affects the operation of the network or violates the rules and
335 | protocols for communication across the network.
336 |
337 | Corresponding Source conveyed, and Installation Information provided,
338 | in accord with this section must be in a format that is publicly
339 | documented (and with an implementation available to the public in
340 | source code form), and must require no special password or key for
341 | unpacking, reading or copying.
342 |
343 | 7. Additional Terms.
344 |
345 | "Additional permissions" are terms that supplement the terms of this
346 | License by making exceptions from one or more of its conditions.
347 | Additional permissions that are applicable to the entire Program shall
348 | be treated as though they were included in this License, to the extent
349 | that they are valid under applicable law. If additional permissions
350 | apply only to part of the Program, that part may be used separately
351 | under those permissions, but the entire Program remains governed by
352 | this License without regard to the additional permissions.
353 |
354 | When you convey a copy of a covered work, you may at your option
355 | remove any additional permissions from that copy, or from any part of
356 | it. (Additional permissions may be written to require their own
357 | removal in certain cases when you modify the work.) You may place
358 | additional permissions on material, added by you to a covered work,
359 | for which you have or can give appropriate copyright permission.
360 |
361 | Notwithstanding any other provision of this License, for material you
362 | add to a covered work, you may (if authorized by the copyright holders of
363 | that material) supplement the terms of this License with terms:
364 |
365 | a) Disclaiming warranty or limiting liability differently from the
366 | terms of sections 15 and 16 of this License; or
367 |
368 | b) Requiring preservation of specified reasonable legal notices or
369 | author attributions in that material or in the Appropriate Legal
370 | Notices displayed by works containing it; or
371 |
372 | c) Prohibiting misrepresentation of the origin of that material, or
373 | requiring that modified versions of such material be marked in
374 | reasonable ways as different from the original version; or
375 |
376 | d) Limiting the use for publicity purposes of names of licensors or
377 | authors of the material; or
378 |
379 | e) Declining to grant rights under trademark law for use of some
380 | trade names, trademarks, or service marks; or
381 |
382 | f) Requiring indemnification of licensors and authors of that
383 | material by anyone who conveys the material (or modified versions of
384 | it) with contractual assumptions of liability to the recipient, for
385 | any liability that these contractual assumptions directly impose on
386 | those licensors and authors.
387 |
388 | All other non-permissive additional terms are considered "further
389 | restrictions" within the meaning of section 10. If the Program as you
390 | received it, or any part of it, contains a notice stating that it is
391 | governed by this License along with a term that is a further
392 | restriction, you may remove that term. If a license document contains
393 | a further restriction but permits relicensing or conveying under this
394 | License, you may add to a covered work material governed by the terms
395 | of that license document, provided that the further restriction does
396 | not survive such relicensing or conveying.
397 |
398 | If you add terms to a covered work in accord with this section, you
399 | must place, in the relevant source files, a statement of the
400 | additional terms that apply to those files, or a notice indicating
401 | where to find the applicable terms.
402 |
403 | Additional terms, permissive or non-permissive, may be stated in the
404 | form of a separately written license, or stated as exceptions;
405 | the above requirements apply either way.
406 |
407 | 8. Termination.
408 |
409 | You may not propagate or modify a covered work except as expressly
410 | provided under this License. Any attempt otherwise to propagate or
411 | modify it is void, and will automatically terminate your rights under
412 | this License (including any patent licenses granted under the third
413 | paragraph of section 11).
414 |
415 | However, if you cease all violation of this License, then your
416 | license from a particular copyright holder is reinstated (a)
417 | provisionally, unless and until the copyright holder explicitly and
418 | finally terminates your license, and (b) permanently, if the copyright
419 | holder fails to notify you of the violation by some reasonable means
420 | prior to 60 days after the cessation.
421 |
422 | Moreover, your license from a particular copyright holder is
423 | reinstated permanently if the copyright holder notifies you of the
424 | violation by some reasonable means, this is the first time you have
425 | received notice of violation of this License (for any work) from that
426 | copyright holder, and you cure the violation prior to 30 days after
427 | your receipt of the notice.
428 |
429 | Termination of your rights under this section does not terminate the
430 | licenses of parties who have received copies or rights from you under
431 | this License. If your rights have been terminated and not permanently
432 | reinstated, you do not qualify to receive new licenses for the same
433 | material under section 10.
434 |
435 | 9. Acceptance Not Required for Having Copies.
436 |
437 | You are not required to accept this License in order to receive or
438 | run a copy of the Program. Ancillary propagation of a covered work
439 | occurring solely as a consequence of using peer-to-peer transmission
440 | to receive a copy likewise does not require acceptance. However,
441 | nothing other than this License grants you permission to propagate or
442 | modify any covered work. These actions infringe copyright if you do
443 | not accept this License. Therefore, by modifying or propagating a
444 | covered work, you indicate your acceptance of this License to do so.
445 |
446 | 10. Automatic Licensing of Downstream Recipients.
447 |
448 | Each time you convey a covered work, the recipient automatically
449 | receives a license from the original licensors, to run, modify and
450 | propagate that work, subject to this License. You are not responsible
451 | for enforcing compliance by third parties with this License.
452 |
453 | An "entity transaction" is a transaction transferring control of an
454 | organization, or substantially all assets of one, or subdividing an
455 | organization, or merging organizations. If propagation of a covered
456 | work results from an entity transaction, each party to that
457 | transaction who receives a copy of the work also receives whatever
458 | licenses to the work the party's predecessor in interest had or could
459 | give under the previous paragraph, plus a right to possession of the
460 | Corresponding Source of the work from the predecessor in interest, if
461 | the predecessor has it or can get it with reasonable efforts.
462 |
463 | You may not impose any further restrictions on the exercise of the
464 | rights granted or affirmed under this License. For example, you may
465 | not impose a license fee, royalty, or other charge for exercise of
466 | rights granted under this License, and you may not initiate litigation
467 | (including a cross-claim or counterclaim in a lawsuit) alleging that
468 | any patent claim is infringed by making, using, selling, offering for
469 | sale, or importing the Program or any portion of it.
470 |
471 | 11. Patents.
472 |
473 | A "contributor" is a copyright holder who authorizes use under this
474 | License of the Program or a work on which the Program is based. The
475 | work thus licensed is called the contributor's "contributor version".
476 |
477 | A contributor's "essential patent claims" are all patent claims
478 | owned or controlled by the contributor, whether already acquired or
479 | hereafter acquired, that would be infringed by some manner, permitted
480 | by this License, of making, using, or selling its contributor version,
481 | but do not include claims that would be infringed only as a
482 | consequence of further modification of the contributor version. For
483 | purposes of this definition, "control" includes the right to grant
484 | patent sublicenses in a manner consistent with the requirements of
485 | this License.
486 |
487 | Each contributor grants you a non-exclusive, worldwide, royalty-free
488 | patent license under the contributor's essential patent claims, to
489 | make, use, sell, offer for sale, import and otherwise run, modify and
490 | propagate the contents of its contributor version.
491 |
492 | In the following three paragraphs, a "patent license" is any express
493 | agreement or commitment, however denominated, not to enforce a patent
494 | (such as an express permission to practice a patent or covenant not to
495 | sue for patent infringement). To "grant" such a patent license to a
496 | party means to make such an agreement or commitment not to enforce a
497 | patent against the party.
498 |
499 | If you convey a covered work, knowingly relying on a patent license,
500 | and the Corresponding Source of the work is not available for anyone
501 | to copy, free of charge and under the terms of this License, through a
502 | publicly available network server or other readily accessible means,
503 | then you must either (1) cause the Corresponding Source to be so
504 | available, or (2) arrange to deprive yourself of the benefit of the
505 | patent license for this particular work, or (3) arrange, in a manner
506 | consistent with the requirements of this License, to extend the patent
507 | license to downstream recipients. "Knowingly relying" means you have
508 | actual knowledge that, but for the patent license, your conveying the
509 | covered work in a country, or your recipient's use of the covered work
510 | in a country, would infringe one or more identifiable patents in that
511 | country that you have reason to believe are valid.
512 |
513 | If, pursuant to or in connection with a single transaction or
514 | arrangement, you convey, or propagate by procuring conveyance of, a
515 | covered work, and grant a patent license to some of the parties
516 | receiving the covered work authorizing them to use, propagate, modify
517 | or convey a specific copy of the covered work, then the patent license
518 | you grant is automatically extended to all recipients of the covered
519 | work and works based on it.
520 |
521 | A patent license is "discriminatory" if it does not include within
522 | the scope of its coverage, prohibits the exercise of, or is
523 | conditioned on the non-exercise of one or more of the rights that are
524 | specifically granted under this License. You may not convey a covered
525 | work if you are a party to an arrangement with a third party that is
526 | in the business of distributing software, under which you make payment
527 | to the third party based on the extent of your activity of conveying
528 | the work, and under which the third party grants, to any of the
529 | parties who would receive the covered work from you, a discriminatory
530 | patent license (a) in connection with copies of the covered work
531 | conveyed by you (or copies made from those copies), or (b) primarily
532 | for and in connection with specific products or compilations that
533 | contain the covered work, unless you entered into that arrangement,
534 | or that patent license was granted, prior to 28 March 2007.
535 |
536 | Nothing in this License shall be construed as excluding or limiting
537 | any implied license or other defenses to infringement that may
538 | otherwise be available to you under applicable patent law.
539 |
540 | 12. No Surrender of Others' Freedom.
541 |
542 | If conditions are imposed on you (whether by court order, agreement or
543 | otherwise) that contradict the conditions of this License, they do not
544 | excuse you from the conditions of this License. If you cannot convey a
545 | covered work so as to satisfy simultaneously your obligations under this
546 | License and any other pertinent obligations, then as a consequence you may
547 | not convey it at all. For example, if you agree to terms that obligate you
548 | to collect a royalty for further conveying from those to whom you convey
549 | the Program, the only way you could satisfy both those terms and this
550 | License would be to refrain entirely from conveying the Program.
551 |
552 | 13. Use with the GNU Affero General Public License.
553 |
554 | Notwithstanding any other provision of this License, you have
555 | permission to link or combine any covered work with a work licensed
556 | under version 3 of the GNU Affero General Public License into a single
557 | combined work, and to convey the resulting work. The terms of this
558 | License will continue to apply to the part which is the covered work,
559 | but the special requirements of the GNU Affero General Public License,
560 | section 13, concerning interaction through a network will apply to the
561 | combination as such.
562 |
563 | 14. Revised Versions of this License.
564 |
565 | The Free Software Foundation may publish revised and/or new versions of
566 | the GNU General Public License from time to time. Such new versions will
567 | be similar in spirit to the present version, but may differ in detail to
568 | address new problems or concerns.
569 |
570 | Each version is given a distinguishing version number. If the
571 | Program specifies that a certain numbered version of the GNU General
572 | Public License "or any later version" applies to it, you have the
573 | option of following the terms and conditions either of that numbered
574 | version or of any later version published by the Free Software
575 | Foundation. If the Program does not specify a version number of the
576 | GNU General Public License, you may choose any version ever published
577 | by the Free Software Foundation.
578 |
579 | If the Program specifies that a proxy can decide which future
580 | versions of the GNU General Public License can be used, that proxy's
581 | public statement of acceptance of a version permanently authorizes you
582 | to choose that version for the Program.
583 |
584 | Later license versions may give you additional or different
585 | permissions. However, no additional obligations are imposed on any
586 | author or copyright holder as a result of your choosing to follow a
587 | later version.
588 |
589 | 15. Disclaimer of Warranty.
590 |
591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
599 |
600 | 16. Limitation of Liability.
601 |
602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
610 | SUCH DAMAGES.
611 |
612 | 17. Interpretation of Sections 15 and 16.
613 |
614 | If the disclaimer of warranty and limitation of liability provided
615 | above cannot be given local legal effect according to their terms,
616 | reviewing courts shall apply local law that most closely approximates
617 | an absolute waiver of all civil liability in connection with the
618 | Program, unless a warranty or assumption of liability accompanies a
619 | copy of the Program in return for a fee.
620 |
621 | END OF TERMS AND CONDITIONS
622 |
623 | How to Apply These Terms to Your New Programs
624 |
625 | If you develop a new program, and you want it to be of the greatest
626 | possible use to the public, the best way to achieve this is to make it
627 | free software which everyone can redistribute and change under these terms.
628 |
629 | To do so, attach the following notices to the program. It is safest
630 | to attach them to the start of each source file to most effectively
631 | state the exclusion of warranty; and each file should have at least
632 | the "copyright" line and a pointer to where the full notice is found.
633 |
634 | {one line to give the program's name and a brief idea of what it does.}
635 | Copyright (C) {year} {name of author}
636 |
637 | This program is free software: you can redistribute it and/or modify
638 | it under the terms of the GNU General Public License as published by
639 | the Free Software Foundation, either version 3 of the License, or
640 | (at your option) any later version.
641 |
642 | This program is distributed in the hope that it will be useful,
643 | but WITHOUT ANY WARRANTY; without even the implied warranty of
644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
645 | GNU General Public License for more details.
646 |
647 | You should have received a copy of the GNU General Public License
648 | along with this program. If not, see .
649 |
650 | Also add information on how to contact you by electronic and paper mail.
651 |
652 | If the program does terminal interaction, make it output a short
653 | notice like this when it starts in an interactive mode:
654 |
655 | {project} Copyright (C) {year} {fullname}
656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
657 | This is free software, and you are welcome to redistribute it
658 | under certain conditions; type `show c' for details.
659 |
660 | The hypothetical commands `show w' and `show c' should show the appropriate
661 | parts of the General Public License. Of course, your program's commands
662 | might be different; for a GUI interface, you would use an "about box".
663 |
664 | You should also get your employer (if you work as a programmer) or school,
665 | if any, to sign a "copyright disclaimer" for the program, if necessary.
666 | For more information on this, and how to apply and follow the GNU GPL, see
667 | .
668 |
669 | The GNU General Public License does not permit incorporating your program
670 | into proprietary programs. If your program is a subroutine library, you
671 | may consider it more useful to permit linking proprietary applications with
672 | the library. If this is what you want to do, use the GNU Lesser General
673 | Public License instead of this License. But first, please read
674 | .
675 |
--------------------------------------------------------------------------------