├── po
├── LINGUAS
├── meson.build
└── POTFILES
├── data
├── com.gitlab.nvlgit.Balss.desktop.in
├── com.gitlab.nvlgit.Balss.appdata.xml.in
├── icons
│ ├── hicolor
│ │ ├── symbolic
│ │ │ └── apps
│ │ │ │ └── com.gitlab.nvlgit.Balss-symbolic.svg
│ │ ├── 16x16
│ │ │ └── apps
│ │ │ │ └── com.gitlab.nvlgit.Balss.svg
│ │ ├── 24x24
│ │ │ └── apps
│ │ │ │ └── com.gitlab.nvlgit.Balss.svg
│ │ ├── 32x32
│ │ │ └── apps
│ │ │ │ └── com.gitlab.nvlgit.Balss.svg
│ │ ├── 64x64
│ │ │ └── apps
│ │ │ │ └── com.gitlab.nvlgit.Balss.svg
│ │ ├── 128x128
│ │ │ └── apps
│ │ │ │ └── com.gitlab.nvlgit.Balss.svg
│ │ └── scalable
│ │ │ └── apps
│ │ │ └── com.gitlab.nvlgit.Balss.svg
│ └── meson.build
├── meson.build
└── com.gitlab.nvlgit.Balss.gschema.xml
├── src
├── ui
│ ├── media-playback-rate-prefix-symbolic.svg
│ ├── media-playback-rate-symbolic.svg
│ ├── balss-controls-symbolic.svg
│ ├── style.css
│ ├── chapter-list-box.ui
│ ├── chapter-row.ui
│ ├── shortcuts-window.ui
│ ├── chapter-indicator.ui
│ ├── control-button.ui
│ └── window.ui
├── balss.gresource.xml
├── main.vala
├── about-dialog.vala
├── meson.build
├── chapter-row.vala
├── app-prefs.vala
├── chapter-list-box.vala
├── chapter-indicator.vala
├── prefs-page.vala
├── app.vala
├── control-button.vala
├── vapi
│ └── mpv.vapi
├── mpv-wrapper-backend.vala
└── window.vala
├── meson.build
├── README.md
├── com.gitlab.nvlgit.Balss.yml
└── LICENSE
/po/LINGUAS:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/po/meson.build:
--------------------------------------------------------------------------------
1 | i18n.gettext('balss', preset: 'glib')
2 |
--------------------------------------------------------------------------------
/data/com.gitlab.nvlgit.Balss.desktop.in:
--------------------------------------------------------------------------------
1 | [Desktop Entry]
2 | Name=Balss
3 | Exec=@app_id@
4 | Terminal=false
5 | Type=Application
6 | Icon=@app_id@
7 | Categories=GTK;GNOME;AudioVideo;Player;
8 | StartupNotify=true
9 | Comment=Simple audio book player
--------------------------------------------------------------------------------
/src/ui/media-playback-rate-prefix-symbolic.svg:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
--------------------------------------------------------------------------------
/po/POTFILES:
--------------------------------------------------------------------------------
1 | data/com.gitlab.nvlgit.Balss.desktop.in
2 | data/com.gitlab.nvlgit.Balss.appdata.xml.in
3 | data/com.gitlab.nvlgit.Balss.gschema.xml
4 | src/window.ui
5 | src/app-menu.ui
6 | src/prefs.ui
7 | src/chapter-list-box.ui
8 | src/app.vala
9 | src/window.vala
10 | src/app-about.vala
11 |
--------------------------------------------------------------------------------
/data/com.gitlab.nvlgit.Balss.appdata.xml.in:
--------------------------------------------------------------------------------
1 |
2 |
3 | com.gitlab.nvlgit.Balss.desktop
4 | CC0-1.0
5 | GPL-3.0-or-later
6 |
7 |
8 |
--------------------------------------------------------------------------------
/data/icons/hicolor/symbolic/apps/com.gitlab.nvlgit.Balss-symbolic.svg:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
--------------------------------------------------------------------------------
/data/icons/meson.build:
--------------------------------------------------------------------------------
1 | icon_sizes = ['16x16', '24x24', '32x32', '64x64', '128x128', 'scalable']
2 |
3 | foreach s: icon_sizes
4 | install_data(
5 | join_paths('hicolor', s, 'apps', app_id + '.svg'),
6 | install_dir: join_paths(get_option('datadir'), 'icons', 'hicolor', s, 'apps')
7 | )
8 | endforeach
9 |
10 | install_data(
11 | join_paths('hicolor', 'symbolic', 'apps', app_id + '-symbolic.svg'),
12 | install_dir: join_paths(datadir, 'icons', 'hicolor', 'symbolic', 'apps')
13 | )
--------------------------------------------------------------------------------
/src/balss.gresource.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | window.ui
5 | prefs-page.ui
6 | chapter-row.ui
7 | chapter-list-box.ui
8 | chapter-indicator.ui
9 | control-button.ui
10 | shortcuts-window.ui
11 | style.css
12 |
13 |
14 | media-playback-rate-symbolic.svg
15 | media-playback-rate-prefix-symbolic.svg
16 | balss-controls-symbolic.svg
17 |
18 |
19 |
--------------------------------------------------------------------------------
/meson.build:
--------------------------------------------------------------------------------
1 | project('Balss', ['c', 'vala'],
2 | version: '0.1.0',
3 | meson_version: '>= 0.40.0',
4 | )
5 |
6 | app_id = 'com.gitlab.nvlgit.' + meson.project_name()
7 |
8 | i18n = import('i18n')
9 |
10 | # Directory variables
11 | bindir = get_option('bindir')
12 | datadir = get_option('datadir')
13 | desktopdir = join_paths(datadir, 'applications')
14 | localedir = get_option('localedir')
15 | po_dir = join_paths(meson.source_root(), 'po')
16 | prefix = get_option('prefix')
17 |
18 | doc_subdir = join_paths(datadir, 'doc', meson.project_name())
19 | install_data('README.md',
20 | install_dir: doc_subdir)
21 |
22 | licenses_subdir = join_paths(datadir, 'licenses', meson.project_name())
23 | install_data('LICENSE',
24 | install_dir: licenses_subdir)
25 |
26 |
27 | subdir('data')
28 | subdir('src')
29 | #subdir('po')
30 |
31 | meson.add_install_script('build-aux/meson/postinstall.py')
--------------------------------------------------------------------------------
/src/ui/media-playback-rate-symbolic.svg:
--------------------------------------------------------------------------------
1 |
2 |
18 |
19 |
--------------------------------------------------------------------------------
/src/ui/balss-controls-symbolic.svg:
--------------------------------------------------------------------------------
1 |
2 |
16 |
17 |
--------------------------------------------------------------------------------
/src/main.vala:
--------------------------------------------------------------------------------
1 | /* main.vala
2 | *
3 | * Copyright (C) 2018 Nick
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | [CCode(cname="VERSION")] extern const string VERSION;
20 | [CCode(cname="APP_ID")] extern const string APP_ID;
21 | [CCode(cname="GETTEXT_PACKAGE")] extern const string GETTEXT_PACKAGE;
22 | [CCode(cname="LOCALEDIR")] extern const string LOCALEDIR;
23 |
24 |
25 | public static int main (string[] args) {
26 |
27 | Intl.setlocale (LocaleCategory.ALL, "");
28 | Intl.bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR);
29 | Intl.bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
30 | Intl.textdomain (GETTEXT_PACKAGE);
31 | GLib.Environment.set_prgname (APP_ID);
32 |
33 | var app = new Balss.App ();
34 | return app.run (args);
35 | }
36 |
--------------------------------------------------------------------------------
/src/ui/style.css:
--------------------------------------------------------------------------------
1 | .balss-listbox-background {
2 | background-color: alpha ( @theme_bg_color, 0.5 );
3 | }
4 |
5 | .balss-seekbar trough {
6 | background-color: alpha ( @theme_selected_fg_color, 0.1 );
7 | background-image: none;
8 | background-size: cover;
9 | background-clip: content-box;
10 | border-color: alpha ( @theme_selected_fg_color, 0.7 );
11 | }
12 |
13 | .balss-seekbar trough highlight {
14 | background-image: image(alpha (@theme_selected_fg_color, 0.9) );
15 | background-size: cover;
16 | background-clip: content-box;
17 | border-color: alpha ( @theme_selected_fg_color, 0.7 );
18 |
19 | }
20 |
21 | .balss-seekbar slider {
22 | box-shadow: none;
23 | border-color: transparent;
24 | background-color: transparent;
25 | background-image: none;
26 | background-size: cover;
27 | }
28 |
29 | .balss-seekbar slider:active,
30 | .balss-seekbar slider:hover,
31 | .balss-seekbar:hover slider {
32 | background-color: @theme_selected_fg_color;
33 | border-color: alpha ( black, 0.2 );
34 | }
35 |
36 | /* workaraund for linked style */
37 | .balss-frame border {
38 | border-right-width: 0px;
39 |
40 | }
41 |
42 | .balss-active-chapter-row {
43 | background-color: @theme_selected_bg_color;
44 | color: @theme_selected_fg_color;
45 | }
46 |
47 | .balss-active-chapter-row:backdrop label{
48 | color: @theme_selected_fg_color;
49 | }
50 |
51 | .balss-volume-scale {
52 | padding-left: 0;
53 | padding-right: 0;
54 | }
55 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Balss
2 |
3 |
4 | Balss is an easy to use player for audiobook files with chapters such as m4b or mka. The goal of the Balss is that the following capabilities:
5 | * Easy to use chapter time navigation
6 | * Setup playback rate (including setup default playback rate)
7 | * Capability to resume playback last played file from stopped position
8 |
9 | 
10 |
11 | ### Building and Installation
12 |
13 | ```bash
14 | git clone https://gitlab.com/nvlgit/Balss.git && cd Balss
15 | meson builddir --prefix=/usr && cd builddir
16 | ninja
17 | su -c 'ninja install'
18 | ```
19 | For rpmbuild: balss.spec
20 |
21 | ### Build Dependencies
22 | * gtk+-3.0 >= 3.22
23 | * libmpv >= 0.28
24 | * meson
25 |
26 | ### Run Dependencies
27 | * libmpv >= 0.28
28 |
29 |
30 | ## Flatpak Building and Installation
31 |
32 | Make temp dir
33 | ```bash
34 | mkdir temp && cd temp
35 | ```
36 | Build
37 | ```bash
38 | wget https://gitlab.com/nvlgit/Balss/raw/master/com.gitlab.nvlgit.Balss.yml
39 | flatpak-builder build-dir com.gitlab.nvlgit.Balss.yml
40 | flatpak build-export my-repo build-dir
41 | flatpak build-bundle my-repo Balss.flatpak com.gitlab.nvlgit.Balss
42 | ```
43 | Install
44 | ```bash
45 | flatpak install Balss.flatpak
46 | ```
47 | Remove temp dir
48 | ```bash
49 | cd .. && rm -R temp
50 | ```
51 |
--------------------------------------------------------------------------------
/data/meson.build:
--------------------------------------------------------------------------------
1 | desktop_conf = configuration_data()
2 | desktop_conf.set('app_id', app_id)
3 |
4 |
5 | desktop_file = i18n.merge_file(
6 | 'desktop',
7 | input: configure_file(
8 | input: '@0@.desktop.in'.format(app_id),
9 | output: '@0@.desktop.in'.format(app_id),
10 | configuration: desktop_conf
11 | ),
12 | output: '@0@.desktop'.format(app_id),
13 | install: true,
14 | install_dir: desktopdir,
15 | po_dir: po_dir,
16 | type: 'desktop'
17 | )
18 |
19 | desktop_utils = find_program('desktop-file-validate', required: false)
20 | if desktop_utils.found()
21 | test('Validate desktop file', desktop_utils,
22 | args: [desktop_file]
23 | )
24 | endif
25 |
26 | appstream_file = i18n.merge_file(
27 | input: 'com.gitlab.nvlgit.Balss.appdata.xml.in',
28 | output: 'com.gitlab.nvlgit.Balss.appdata.xml',
29 | po_dir: '../po',
30 | install: true,
31 | install_dir: join_paths(get_option('datadir'), 'appdata')
32 | )
33 |
34 | appstream_util = find_program('appstream-util', required: false)
35 | if appstream_util.found()
36 | test('Validate appstream file', appstream_util,
37 | args: ['validate', appstream_file]
38 | )
39 | endif
40 |
41 | install_data('com.gitlab.nvlgit.Balss.gschema.xml',
42 | install_dir: join_paths(get_option('datadir'), 'glib-2.0/schemas')
43 | )
44 |
45 | compile_schemas = find_program('glib-compile-schemas', required: false)
46 | if compile_schemas.found()
47 | test('Validate schema file', compile_schemas,
48 | args: ['--strict', '--dry-run', meson.current_source_dir()]
49 | )
50 | endif
51 |
52 | subdir('icons')
--------------------------------------------------------------------------------
/src/about-dialog.vala:
--------------------------------------------------------------------------------
1 | /* about-dialog.vala *
2 | *
3 | * Copyright (C) 2018 Nick *
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | namespace Balss {
20 |
21 | public class AppAbout : Gtk.AboutDialog {
22 |
23 | public AppAbout (Gtk.Window window) {
24 |
25 | GLib.Object (transient_for: window, use_header_bar: 1);
26 | this.set_destroy_with_parent (true);
27 | this.set_modal (true);
28 |
29 | logo_icon_name = APP_ID;
30 |
31 | this.program_name = "Balss";
32 |
33 | this.version = VERSION;
34 | this.comments = _("Audiobook player");
35 | this.website = "https://github.com/nvlgit/Balss";
36 | this.website_label = "Balss on gitlab.com";
37 | this.copyright = "Copyright © 2018 Nick";
38 |
39 | this.artists = null;
40 | this.authors = {"Nick", ""};
41 | this.documenters = null;
42 | this.translator_credits = null;
43 |
44 | this.license = "GNU Public Licence version 3";
45 | this.wrap_license = true;
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/meson.build:
--------------------------------------------------------------------------------
1 | balss_sources = [
2 | 'main.vala',
3 | 'app.vala',
4 | 'window.vala',
5 | 'about-dialog.vala',
6 | 'app-prefs.vala',
7 | 'chapter-row.vala',
8 | 'chapter-list-box.vala',
9 | 'chapter-indicator.vala',
10 | 'control-button.vala',
11 | 'mpv-wrapper-backend.vala',
12 | 'prefs-page.vala',
13 | ]
14 |
15 | c_args = [
16 | '-DVERSION="' + meson.project_version() + '"',
17 | '-DAPP_ID="' + app_id + '"',
18 | '-DGETTEXT_PACKAGE="' + app_id + '"',
19 | '-DLOCALEDIR="' + join_paths(prefix, localedir) + '"'
20 | ]
21 |
22 | add_project_arguments(['--vapidir', join_paths(meson.current_source_dir(), 'vapi')],
23 | language: 'vala')
24 |
25 | balss_deps = [
26 | dependency('gio-2.0', version: '>= 2.50'),
27 | dependency('gtk+-3.0', version: '>= 3.22'),
28 | dependency('mpv', version: '>= 1.20'),
29 | ]
30 |
31 | cc = meson.get_compiler('c')
32 | m_dep = cc.find_library('m', required : true)
33 |
34 | gnome = import('gnome')
35 |
36 | add_project_arguments(['--gresourcesdir', join_paths(meson.current_source_dir(), 'ui')],
37 | language: 'vala')
38 |
39 | add_project_arguments(['--gresourcesdir', join_paths(meson.source_root(), 'data', 'icons', 'hicolor', 'scalable', 'apps')],
40 | language: 'vala')
41 |
42 |
43 |
44 | balss_sources += gnome.compile_resources('balss-resources',
45 | 'balss.gresource.xml',
46 | source_dir: join_paths(meson.current_source_dir(), 'ui'),
47 | c_name: 'balss'
48 | )
49 |
50 | executable(app_id, balss_sources,
51 | vala_args: '--target-glib=2.50',
52 | c_args: c_args,
53 | dependencies: [balss_deps, m_dep],
54 | install: true,
55 | )
--------------------------------------------------------------------------------
/data/com.gitlab.nvlgit.Balss.gschema.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | false
7 | Dark theme
8 |
9 | Force use dark theme
10 |
11 |
12 |
13 |
14 | true
15 | Resume playback on startup
16 |
17 | Resume playback the last played URI
18 |
19 |
20 |
21 |
22 | 'none'
23 | Last URI
24 |
25 |
26 |
27 | 0
28 | Last position
29 |
30 |
31 |
32 |
33 | 1.0
34 | Playback rate
35 |
36 | Default playback rate
37 |
38 |
39 |
40 |
41 | false
42 | Show desktop notifications
43 |
44 | Notify about a new chapter
45 |
46 |
47 |
48 |
49 | 540
50 | Window width
51 |
52 |
53 |
54 | 500
55 | Window height
56 |
57 |
58 |
59 |
60 |
--------------------------------------------------------------------------------
/src/ui/chapter-list-box.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | True
7 | False
8 | True
9 | vertical
10 | top
11 |
12 |
16 |
17 | False
18 | True
19 | 0
20 |
21 |
22 |
23 |
44 |
45 | True
46 | True
47 | 1
48 |
49 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/data/icons/hicolor/16x16/apps/com.gitlab.nvlgit.Balss.svg:
--------------------------------------------------------------------------------
1 |
2 |
26 |
27 |
--------------------------------------------------------------------------------
/data/icons/hicolor/24x24/apps/com.gitlab.nvlgit.Balss.svg:
--------------------------------------------------------------------------------
1 |
2 |
31 |
32 |
--------------------------------------------------------------------------------
/src/chapter-row.vala:
--------------------------------------------------------------------------------
1 | /* chapter-row.vala
2 | *
3 | * Copyright 2018 Nick
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | namespace Balss {
20 |
21 | [GtkTemplate (ui = "/com/gitlab/nvlgit/Balss/chapter-row.ui")]
22 | public class ChapterRow : Gtk.ListBoxRow {
23 | [GtkChild] public Gtk.Label number_label;
24 | [GtkChild] private Gtk.Label time_label;
25 | [GtkChild] private Gtk.Label title_label;
26 | [GtkChild] private Gtk.Label duration_label;
27 | [GtkChild] public Gtk.Scale seekbar;
28 | [GtkChild] public Gtk.Revealer revealer;
29 | [GtkChild] public Gtk.Box box;
30 | public signal void seek_changed (double p);
31 |
32 |
33 | [GtkCallback]
34 | private void seekbar_value_changed_cb () {
35 |
36 |
37 | seek_changed (seekbar.get_value () ); //emit signal
38 | }
39 |
40 |
41 | public ChapterRow () {
42 | }
43 |
44 |
45 | public void set_range (double start, double end) {
46 |
47 | this.seekbar.value_changed.disconnect (seekbar_value_changed_cb);
48 | this.seekbar.set_range (start, end);
49 | this.seekbar.value_changed.connect (seekbar_value_changed_cb);
50 | }
51 |
52 |
53 | public void set_position (double p) {
54 |
55 | this.seekbar.value_changed.disconnect (seekbar_value_changed_cb);
56 | this.seekbar.set_value (p);
57 | this.seekbar.value_changed.connect (seekbar_value_changed_cb);
58 | }
59 |
60 |
61 | public string number {
62 |
63 | get { return number_label.get_text (); }
64 | set { number_label.set_text (value); }
65 | }
66 |
67 |
68 | public string title {
69 |
70 | get { return title_label.get_text (); }
71 | set { title_label.set_text (value); }
72 | }
73 |
74 |
75 | public string time {
76 |
77 | get { return time_label.get_text (); }
78 | set { time_label.set_text (value); }
79 | }
80 |
81 |
82 | public string duration {
83 |
84 | get { return duration_label.get_text (); }
85 | set { duration_label.set_text (value); }
86 | }
87 |
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/com.gitlab.nvlgit.Balss.yml:
--------------------------------------------------------------------------------
1 | app-id: com.gitlab.nvlgit.Balss
2 |
3 | runtime: org.gnome.Platform
4 | runtime-version: master
5 |
6 | sdk: org.gnome.Sdk
7 |
8 | command: com.gitlab.nvlgit.Balss
9 |
10 | build-options:
11 | cflags: -O2 -g
12 | cxxflags: -O2 -g
13 |
14 | cleanup:
15 | - "/include"
16 | - "/debug"
17 |
18 | finish-args:
19 | - "--device=dri"
20 | - "--socket=x11"
21 | - "--share=ipc"
22 | - "--socket=wayland"
23 | - "--socket=pulseaudio"
24 | - "--filesystem=host:ro"
25 | - "--filesystem=xdg-run/dconf"
26 | - "--filesystem=~/.config/dconf:ro"
27 | - "--env=DCONF_USER_CONFIG_DIR=.config/dconf"
28 | - "--talk-name=ca.desrt.dconf"
29 |
30 | modules:
31 |
32 | - name: ffmpeg
33 |
34 | sources:
35 | - type: archive
36 | url: https://ffmpeg.org/releases/ffmpeg-4.0.2.tar.xz
37 | sha256: "a95c0cc9eb990e94031d2183f2e6e444cc61c99f6f182d1575c433d62afb2f97"
38 |
39 | config-opts:
40 | - "--disable-everything"
41 | - "--enable-shared"
42 | - "--disable-static"
43 | - "--disable-doc"
44 | - "--disable-programs"
45 | - "--enable-decoder=aac"
46 | - "--enable-decoder=alac"
47 | - "--enable-decoder=ac3"
48 | - "--enable-decoder=mp3"
49 | - "--enable-decoder=vorbis"
50 | - "--enable-decoder=opus"
51 | - "--enable-parser=vorbis"
52 | - "--enable-parser=aac"
53 | - "--enable-parser=ac3"
54 | - "--enable-demuxer=aac"
55 | - "--enable-demuxer=matroska"
56 | - "--enable-demuxer=ac3"
57 | - "--enable-demuxer=mp3"
58 | - "--enable-demuxer=mov"
59 |
60 |
61 | cleanup:
62 |
63 | - "/include"
64 | - "/lib/pkgconfig"
65 | - "/share/ffmpeg/examples"
66 |
67 |
68 | - name: libmpv
69 | sources:
70 |
71 | - type: git
72 | url: https://github.com/mpv-player/mpv.git
73 | tag: v0.28.2
74 |
75 | - type: file
76 | url: https://waf.io/waf-2.0.9
77 | sha256: "2a8e0816f023995e557f79ea8940d322bec18f286917c8f9a6fa2dc3875dfa48"
78 | dest-filename: waf
79 |
80 | buildsystem: simple
81 |
82 | build-commands:
83 |
84 | - python3 waf configure --prefix=/app --libdir=/app/lib --enable-libmpv-shared --disable-cplayer --disable-build-date --disable-android --disable-libass --disable-lua --disable-javascript --disable-cplugins --enable-pulse --disable-vulkan --disable-vdpau --disable-x11 --disable-oss-audio --disable-alsa --disable-manpage-build --disable-debug-build --disable-libbluray
85 | - python3 waf
86 | - python3 waf install
87 |
88 | cleanup:
89 |
90 | - "/include"
91 | - "/lib/pkgconfig"
92 |
93 |
94 | - name: Balss
95 |
96 | sources:
97 | - type: git
98 | url: https://gitlab.com/nvlgit/Balss.git
99 | buildsystem: meson
100 | builddir: true
101 |
102 |
103 |
104 |
--------------------------------------------------------------------------------
/src/app-prefs.vala:
--------------------------------------------------------------------------------
1 | /* app-prefs.vala *
2 | *
3 | * Copyright (C) 2018 Nick *
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | namespace Balss {
20 |
21 |
22 | public class Prefs : GLib.Object{
23 |
24 | private GLib.Settings settings;
25 |
26 | private bool _dark_theme;
27 | private bool _play_last;
28 | private string _last_uri;
29 | private double _last_position;
30 | private double _playback_rate;
31 | private bool _show_notifications;
32 | private int _window_width;
33 | private int _window_height;
34 |
35 |
36 | public Prefs () {
37 |
38 | settings = new GLib.Settings ("com.gitlab.nvlgit.Balss");
39 |
40 | _dark_theme = settings.get_boolean ("dark-theme");
41 | _play_last = settings.get_boolean ("play-last");
42 | _last_uri = settings.get_string ("last-uri");
43 | _last_position = settings.get_double ("last-position");
44 | _playback_rate = settings.get_double ("playback-rate");
45 | _show_notifications = settings.get_boolean ("show-notifications");
46 | _window_width = settings.get_int ("window-width");
47 | _window_height = settings.get_int ("window-height");
48 |
49 | }
50 |
51 | public bool dark_theme {
52 |
53 | get { return _dark_theme; }
54 | set {
55 | _dark_theme = value;
56 | settings.set_boolean ("dark-theme", value);
57 | }
58 | }
59 |
60 | public bool play_last {
61 |
62 | get { return _play_last; }
63 | set {
64 | _play_last = value;
65 | settings.set_boolean ("play-last", value);
66 | }
67 | }
68 |
69 | public string last_uri {
70 |
71 | get { return _last_uri; }
72 | set {
73 | _last_uri = value;
74 | settings.set_string ("last-uri", value);
75 | }
76 | }
77 |
78 | public double last_position {
79 |
80 | get { return _last_position; }
81 | set {
82 | _last_position = value;
83 | settings.set_double ("last-position", value);
84 | }
85 | }
86 |
87 | public double playback_rate {
88 |
89 | get { return _playback_rate; }
90 | set {
91 | _playback_rate = value;
92 | settings.set_double ("playback-rate", value);
93 | }
94 | }
95 |
96 | public bool show_notifications {
97 |
98 | get { return _show_notifications; }
99 | set {
100 | _show_notifications = value;
101 | settings.set_boolean ("show-notifications", value);
102 | }
103 | }
104 |
105 | public int window_width {
106 |
107 | get { return _window_width; }
108 | set {
109 | _window_width = value;
110 | settings.set_int ("window-width", value);
111 | }
112 | }
113 |
114 | public int window_height {
115 |
116 | get { return _window_height; }
117 | set {
118 | _window_height = value;
119 | settings.set_int ("window-height", value);
120 | }
121 | }
122 | }
123 |
124 | }
125 |
--------------------------------------------------------------------------------
/data/icons/hicolor/32x32/apps/com.gitlab.nvlgit.Balss.svg:
--------------------------------------------------------------------------------
1 |
2 |
40 |
41 |
--------------------------------------------------------------------------------
/src/chapter-list-box.vala:
--------------------------------------------------------------------------------
1 | /* chapter-list-box.vala
2 | *
3 | * Copyright 2018 Nick
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | namespace Balss {
20 |
21 | [GtkTemplate (ui = "/com/gitlab/nvlgit/Balss/chapter-list-box.ui")]
22 | public class ChapterListBox : Gtk.Box {
23 |
24 | [GtkChild] private Gtk.ScrolledWindow scrolled_window;
25 | [GtkChild] private Gtk.ListBox list;
26 | private Gtk.SizeGroup sizegroup;
27 | private int last_index;
28 | public signal void seek_changed (double pos);
29 |
30 |
31 | public signal void chapter_row_activated (int index);
32 |
33 | construct {
34 | sizegroup = new Gtk.SizeGroup (Gtk.SizeGroupMode.HORIZONTAL);
35 | last_index = 0;
36 | //this.list.set_focus_child.connect (set_focus_child_cb);
37 |
38 | }
39 |
40 | public ChapterListBox () {}
41 |
42 |
43 | public void set_range (double offset, double end) {
44 |
45 | Gtk.ListBoxRow row = this.list.get_row_at_index (last_index);
46 | ( (Balss.ChapterRow) row).set_range (offset, end);
47 | }
48 |
49 | public void set_time (string time) {
50 |
51 | Gtk.ListBoxRow row = this.list.get_row_at_index (last_index);
52 | ( (Balss.ChapterRow) row).time = time + " / ";
53 | }
54 |
55 | public void set_position (double p) {
56 |
57 | Gtk.ListBoxRow row = this.list.get_row_at_index (last_index);
58 | ( (Balss.ChapterRow) row).set_position (p);
59 | }
60 |
61 | private void add_separators () {
62 |
63 | this.list.set_header_func (update_header_fn);
64 |
65 | }
66 | private void update_header_fn (Gtk.ListBoxRow row, Gtk.ListBoxRow? before){
67 |
68 | Gtk.Widget? current;
69 |
70 | if (before == null) {
71 | row.set_header (null);
72 | return;
73 | }
74 |
75 | current = row.get_header ();
76 | if (current == null) {
77 | current = new Gtk.Separator (Gtk.Orientation.HORIZONTAL);
78 | current.show ();
79 | row.set_header (current);
80 | }
81 |
82 | }
83 |
84 | public void add_row (Balss.ChapterRow row){
85 |
86 | this.list.add (row);
87 | add_separators ();
88 | sizegroup.add_widget (row.number_label);
89 | row.seek_changed.connect ( (p) => {
90 | seek_changed (p); // Emit signal
91 | });
92 | }
93 |
94 | [GtkCallback]
95 | private void list_box_row_activated_cb (Gtk.ListBoxRow row) {
96 |
97 | int i = row.get_index ();
98 | chapter_row_activated (i); //emit signal
99 | }
100 |
101 | public void mark_row_at_index (int i) {
102 |
103 | Gtk.ListBoxRow last_row = this.list.get_row_at_index (last_index);
104 | ( (Balss.ChapterRow) last_row).time = "";
105 | ( (Balss.ChapterRow) last_row).revealer.set_reveal_child (false);
106 | Gtk.StyleContext last_ctx = ( (Balss.ChapterRow) last_row).get_style_context ();
107 | last_ctx.remove_class ("balss-active-chapter-row");
108 |
109 | Gtk.ListBoxRow row = this.list.get_row_at_index (i);
110 |
111 | ( (Balss.ChapterRow) row).revealer.set_reveal_child (true);
112 | Gtk.StyleContext ctx = ( (Balss.ChapterRow) row).get_style_context ();
113 | ctx.add_class ("balss-active-chapter-row");
114 | this.last_index = i;
115 | GLib.Timeout.add (150, () => { // delay while revealer revealed
116 | row.grab_focus ();
117 | return false;
118 | });
119 | }
120 | }
121 | }
122 |
--------------------------------------------------------------------------------
/src/chapter-indicator.vala:
--------------------------------------------------------------------------------
1 | /* chapter-indicator.vala
2 | *
3 | * Copyright 2018 Nick
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 |
20 | namespace Balss {
21 |
22 | [GtkTemplate (ui = "/com/gitlab/nvlgit/Balss/chapter-indicator.ui")]
23 | public class ChapterIndicator : Gtk.Box {
24 |
25 | [GtkChild] private Gtk.DrawingArea area;
26 | [GtkChild] private Gtk.EventBox event_box;
27 | [GtkChild] private Gtk.Frame frame;
28 | [GtkChild] private Gtk.Popover indicator_popover;
29 | [GtkChild] private Gtk.Label label;
30 | [GtkChild] private Gtk.Grid tooltip_grid;
31 | [GtkChild] private Gtk.Label tooltip_persent_label;
32 | [GtkChild] private Gtk.Label tooltip_time_label;
33 | [GtkChild] private Gtk.Button button_previous;
34 | [GtkChild] private Gtk.Button button_next;
35 | private double _value;
36 | public signal void button_clicked (int direction);
37 |
38 | construct {
39 |
40 | this._value = 0;
41 | this.area.draw.connect (draw_cb);
42 | this.event_box.events |= Gdk.EventMask.BUTTON_PRESS_MASK;
43 | this.event_box.button_press_event.connect ((event) => {
44 | debug ("button_press_event.connect");
45 | indicator_popover.popup ();
46 | return true;
47 | });
48 | this.frame.has_tooltip = true;
49 | this.frame.query_tooltip.connect ((x, y, keyboard_tooltip, tooltip) => {
50 | tooltip.set_custom (tooltip_grid);
51 | return true;
52 | });
53 | }
54 |
55 | public ChapterIndicator () {}
56 |
57 | public void clear () {
58 |
59 | this.button_next.sensitive = false;
60 | this.button_previous.sensitive = false;
61 | this.label.label = "";
62 | set_fraction (0);
63 | }
64 |
65 | public void set_info (int chapter, int count) {
66 |
67 | this.label.label = "%d / %d".printf (chapter, count);
68 | this.button_previous.sensitive = (chapter == 1) ? false : true;
69 | this.button_next.sensitive = (chapter == count) ? false : true;
70 | }
71 |
72 | public void set_tooltip (int persent, string pos, string dur) {
73 |
74 | tooltip_persent_label.label = "%d %%".printf (persent);
75 | tooltip_time_label.label = "%s / %s".printf (pos, dur);
76 | }
77 |
78 | public void set_fraction (double val) {
79 |
80 | if (val > 1)
81 | val = 1;
82 | this._value = val;
83 |
84 | this.area.queue_draw ();
85 | }
86 |
87 | private bool draw_cb (Gtk.Widget da, Cairo.Context cr) {
88 |
89 | uint w;
90 | uint h;
91 | Gdk.RGBA color;
92 | Gdk.RGBA selected_color;
93 | Gtk.StyleContext style_context;
94 |
95 | style_context = area.get_style_context ();
96 | color = style_context.get_color ( area.get_state_flags () );
97 | bool result = style_context.lookup_color ("theme_selected_bg_color",
98 | out selected_color);
99 | if (!result)
100 | return false;
101 |
102 | double sum = color.red + color.green + color.blue;
103 | selected_color.alpha *= (sum < 1) ? 0.2 : 0.5;
104 |
105 | if (_value > 0) {
106 | _value = double.max (0.01, _value);
107 | }
108 |
109 | if (_value > 0.99 && _value < 1.0 ) {
110 | _value = 0.99;
111 | }
112 |
113 | w = area.get_allocated_width ();
114 | h = area.get_allocated_height ();
115 |
116 | Gdk.cairo_set_source_rgba (cr, selected_color);
117 | cr.rectangle(0, 0, w * _value, h);
118 | cr.fill ();
119 |
120 | return true;
121 | }
122 | [GtkCallback] private void button_next_clicked_cb () {
123 | button_clicked (1);
124 | }
125 | [GtkCallback] private void button_previous_clicked_cb () {
126 | button_clicked (-1);
127 | }
128 | }
129 | }
130 |
--------------------------------------------------------------------------------
/src/prefs-page.vala:
--------------------------------------------------------------------------------
1 | /* prefs-page.vala
2 | *
3 | * Copyright (C) 2018 Nick
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 |
20 |
21 | namespace Balss {
22 |
23 | [GtkTemplate (ui = "/com/gitlab/nvlgit/Balss/prefs-page.ui")]
24 | public class PrefsPage : Gtk.Box {
25 |
26 |
27 | [GtkChild] private Gtk.Switch play_last_switch;
28 | [GtkChild] private Gtk.SpinButton rate_spinbutton;
29 | [GtkChild] private Gtk.Switch dark_theme_switch;
30 | [GtkChild] private Gtk.Switch mpris_switch;
31 | [GtkChild] private Gtk.Switch notification_switch;
32 | [GtkChild] private Gtk.ListBox pb_listbox;
33 | [GtkChild] private Gtk.ListBoxRow play_last_row;
34 | [GtkChild] private Gtk.ListBoxRow rate_row;
35 | [GtkChild] private Gtk.ListBox appearence_listbox;
36 | [GtkChild] private Gtk.ListBoxRow dark_theme_row;
37 | [GtkChild] private Gtk.ListBox features_listbox;
38 | [GtkChild] private Gtk.ListBoxRow mpris_row;
39 | [GtkChild] private Gtk.ListBoxRow notify_row;
40 |
41 |
42 |
43 | construct {
44 |
45 | add_separators (this.pb_listbox);
46 | add_separators (this.features_listbox);
47 | connect_prefs_signals ();
48 | connect_rows_signals ();
49 | }
50 |
51 |
52 |
53 | public PrefsPage () {
54 |
55 | dark_theme_switch.active = App.preferences.dark_theme;
56 | notification_switch.active = App.preferences.show_notifications;
57 | play_last_switch.active = App.preferences.play_last;
58 | rate_spinbutton.value = App.preferences.playback_rate;
59 | }
60 |
61 |
62 |
63 | public void update_gtk_theme () {
64 |
65 | var gtk_settings = Gtk.Settings.get_default ();
66 | gtk_settings.gtk_application_prefer_dark_theme = App.preferences.dark_theme;
67 | }
68 |
69 |
70 |
71 | private void add_separators (Gtk.ListBox lbox) {
72 |
73 | lbox.set_header_func (update_header_fn);
74 | }
75 |
76 |
77 |
78 | private void update_header_fn (Gtk.ListBoxRow row, Gtk.ListBoxRow? before){
79 |
80 | Gtk.Widget? current;
81 |
82 | if (before == null) {
83 | row.set_header (null);
84 | return;
85 | }
86 |
87 | current = row.get_header ();
88 | if (current == null) {
89 | current = new Gtk.Separator (Gtk.Orientation.HORIZONTAL);
90 | current.show ();
91 | row.set_header (current);
92 | }
93 | }
94 |
95 |
96 |
97 | private void connect_prefs_signals () {
98 |
99 | App.preferences.notify["dark-theme"].connect (update_gtk_theme);
100 |
101 | dark_theme_switch.state_set.connect( () => {
102 | App.preferences.dark_theme = dark_theme_switch.active;
103 | return false;
104 | });
105 |
106 | play_last_switch.state_set.connect( () => {
107 | App.preferences.play_last = play_last_switch.active;
108 | return false;
109 | });
110 |
111 | notification_switch.state_set.connect( () => {
112 | App.preferences.show_notifications = notification_switch.active;
113 | return false;
114 | });
115 |
116 | rate_spinbutton.value_changed.connect( () => {
117 | App.preferences.playback_rate = rate_spinbutton.value;
118 | });
119 | }
120 |
121 |
122 | private void connect_rows_signals () {
123 |
124 | this.pb_listbox.row_activated.connect ( (row) => {
125 |
126 | if (row == play_last_row)
127 | play_last_switch.active = !play_last_switch.active;
128 | if (row == rate_row)
129 | rate_spinbutton.grab_focus ();
130 | });
131 |
132 | this.appearence_listbox.row_activated.connect ( (row) => {
133 |
134 | if (row == dark_theme_row)
135 | dark_theme_switch.active = !dark_theme_switch.active;
136 | });
137 |
138 | this.features_listbox.row_activated.connect ( (row) => {
139 |
140 | if (row == mpris_row)
141 | mpris_switch.active = !mpris_switch.active;
142 | if (row == notify_row)
143 | notification_switch.active = !notification_switch.active;
144 | });
145 | }
146 |
147 | }
148 | }
149 |
--------------------------------------------------------------------------------
/src/app.vala:
--------------------------------------------------------------------------------
1 | /* app.vala
2 | *
3 | * Copyright (C) 2018 Nick
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 |
20 | namespace Balss {
21 |
22 | const string APP_ID = "com.gitlab.nvlgit.Balss";
23 |
24 |
25 | public class App : Gtk.Application {
26 |
27 |
28 | private PlayerWindow win;
29 | public static Prefs preferences;
30 |
31 | private const ActionEntry[] actions = {
32 |
33 | { "open", open_cb },
34 | { "prefs", prefs_cb },
35 | { "shortcuts", shortcuts_cb },
36 | { "about", about_cb },
37 | { "quit", quit_cb }
38 | };
39 |
40 |
41 |
42 | public App () {
43 |
44 | application_id = APP_ID;
45 | flags |= GLib.ApplicationFlags.HANDLES_OPEN;
46 | }
47 |
48 |
49 |
50 | private void open_cb (SimpleAction action, Variant? parameter) {
51 |
52 | win = (PlayerWindow) this.active_window;
53 | win.show_page ("main");
54 |
55 | var chooser = new Gtk.FileChooserDialog ("Select file",
56 | win as PlayerWindow,
57 | Gtk.FileChooserAction.OPEN,
58 | _("_Cancel"),
59 | Gtk.ResponseType.CANCEL,
60 | _("_Open"),
61 | Gtk.ResponseType.ACCEPT,
62 | null);
63 | var filter = new Gtk.FileFilter ();
64 | filter.set_filter_name (_("m4b books") );
65 | filter.add_pattern ("*.m4b");
66 | filter.add_pattern ("*.M4B");
67 | filter.add_pattern ("*.M4b");
68 | filter.add_pattern ("*.m4B");
69 | chooser.add_filter (filter);
70 |
71 | filter = new Gtk.FileFilter ();
72 | filter.set_filter_name (_("All audio files") );
73 | filter.add_mime_type ("audio/*");
74 | chooser.add_filter (filter);
75 |
76 | filter = new Gtk.FileFilter ();
77 | filter.set_filter_name (_("All files") );
78 | filter.add_pattern ("*");
79 | chooser.add_filter (filter);
80 |
81 | chooser.local_only = true;
82 | chooser.select_multiple = false;
83 | chooser.set_modal (true);
84 |
85 | chooser.response.connect ( (fc, r) => {
86 |
87 | var c = fc as Gtk.FileChooserDialog;
88 | if (r == Gtk.ResponseType.ACCEPT)
89 | win.open (c.get_uri () );
90 | chooser.destroy ();
91 | });
92 |
93 | chooser.show ();
94 | }
95 |
96 |
97 |
98 | private void prefs_cb (SimpleAction action, Variant? parameter) {
99 |
100 | win = (PlayerWindow) this.active_window;
101 | win.show_page ("prefs");
102 | }
103 |
104 |
105 |
106 | private void shortcuts_cb (SimpleAction action, Variant? parameter) {
107 |
108 | var builder = new Gtk.Builder.from_resource (
109 | "/com/gitlab/nvlgit/Balss/shortcuts-window.ui");
110 | var shortcuts_window = (Gtk.Window) builder.get_object ("shortcuts-window");
111 | shortcuts_window.show ();
112 | }
113 |
114 |
115 |
116 | void about_cb (SimpleAction action, Variant? parameter) {
117 |
118 | AppAbout about = new AppAbout (get_active_window () );
119 | about.present ();
120 | }
121 |
122 |
123 |
124 | void quit_cb (SimpleAction action, Variant? parameter) {
125 |
126 | this.win.on_close_window ();
127 | this.quit ();
128 | }
129 |
130 |
131 |
132 | private void notify_desktop (string title, string body) {
133 |
134 | var n = new GLib.Notification (title);
135 | n.set_body (body);
136 | ( (GLib.Application) this).send_notification (null, n);
137 | }
138 |
139 |
140 |
141 | public override void startup () {
142 |
143 | base.startup ();
144 | preferences = new Prefs ();
145 |
146 | var css_provider = new Gtk.CssProvider ();
147 | css_provider.load_from_resource ("/com/gitlab/nvlgit/Balss/style.css");
148 | Gtk.StyleContext.add_provider_for_screen (Gdk.Screen.get_default (),
149 | css_provider,
150 | Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION);
151 |
152 | Gtk.IconTheme icon_theme = Gtk.IconTheme.get_default ();
153 | icon_theme.add_resource_path("/com/gitlab/nvlgit/Balss/icons");
154 |
155 | this.add_action_entries (actions, this);
156 | }
157 |
158 |
159 |
160 | protected override void activate () {
161 |
162 | //base.activate ();
163 | win = (PlayerWindow) this.active_window;
164 | if (win == null) {
165 | win = new PlayerWindow (this);
166 | win.new_notification.connect (notify_desktop);
167 | }
168 | win.present ();
169 | }
170 |
171 |
172 |
173 | public override void open (GLib.File[] files,
174 | string hint) {
175 |
176 | win = (PlayerWindow) this.active_window;
177 | if (win == null)
178 | win = new PlayerWindow (this);
179 |
180 | string uri = files[0].get_uri ();
181 | win.open (uri);
182 | win.present ();
183 | }
184 | }
185 | }
186 |
--------------------------------------------------------------------------------
/src/ui/chapter-row.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | True
7 | True
8 | start
9 | True
10 | False
11 |
12 |
13 | True
14 | False
15 | True
16 | vertical
17 |
18 |
19 | True
20 | False
21 | 6
22 | 6
23 | 6
24 | 18
25 |
26 |
27 | True
28 | False
29 | 1
30 | 1
31 |
32 |
33 |
34 |
35 |
36 | False
37 | True
38 | 0
39 |
40 |
41 |
42 |
43 | True
44 | False
45 |
46 | end
47 | 0
48 |
49 |
50 | True
51 | True
52 | 1
53 |
54 |
55 |
56 |
57 | True
58 | False
59 |
60 |
61 | True
62 | False
63 |
64 |
65 | False
66 | True
67 | 0
68 |
69 |
70 |
71 |
72 | True
73 | False
74 | 00:00
75 | 1
76 |
77 |
78 | False
79 | True
80 | 1
81 |
82 |
83 |
84 |
85 | False
86 | True
87 | 2
88 |
89 |
90 |
91 |
92 | False
93 | True
94 | 0
95 |
96 |
97 |
98 |
99 | True
100 | False
101 | 100
102 |
103 |
104 | True
105 | True
106 | True
107 | False
108 |
109 |
112 |
113 |
114 |
115 |
116 | False
117 | True
118 | 1
119 |
120 |
121 |
122 |
123 |
126 |
127 |
128 |
--------------------------------------------------------------------------------
/data/icons/hicolor/64x64/apps/com.gitlab.nvlgit.Balss.svg:
--------------------------------------------------------------------------------
1 |
2 |
76 |
77 |
--------------------------------------------------------------------------------
/data/icons/hicolor/128x128/apps/com.gitlab.nvlgit.Balss.svg:
--------------------------------------------------------------------------------
1 |
2 |
76 |
77 |
--------------------------------------------------------------------------------
/data/icons/hicolor/scalable/apps/com.gitlab.nvlgit.Balss.svg:
--------------------------------------------------------------------------------
1 |
2 |
76 |
77 |
--------------------------------------------------------------------------------
/src/control-button.vala:
--------------------------------------------------------------------------------
1 | /* control-button.vala
2 | *
3 | * Copyright 2018 Nick
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 |
20 | namespace Balss {
21 |
22 | [GtkTemplate (ui = "/com/gitlab/nvlgit/Balss/control-button.ui")]
23 | public class ControlMenuButton : Gtk.MenuButton {
24 |
25 | [GtkChild] private Gtk.Scale volume_scale;
26 | [GtkChild] private Gtk.SpinButton rate_spinbutton;
27 | [GtkChild] private Gtk.DrawingArea rate_area;
28 | [GtkChild] private Gtk.Image volume_button_image;
29 | [GtkChild] private Gtk.Grid tooltip_grid;
30 | [GtkChild] private Gtk.Label tooltip_rate_label;
31 | [GtkChild] private Gtk.Label tooltip_volume_label;
32 |
33 | public signal void volume_changed (double val);
34 | public signal void rate_changed (double val);
35 |
36 | private double _rate;
37 | private double _volume;
38 |
39 | const string[] ICONS = { "audio-volume-muted-symbolic",
40 | "audio-volume-low-symbolic",
41 | "audio-volume-medium-symbolic",
42 | "audio-volume-high-symbolic" };
43 | [GtkCallback]
44 | private void volume_scale_value_changed_cb (Gtk.Range range) {
45 |
46 | volume_changed (range.get_value () ); //Emit signal
47 | }
48 |
49 | [GtkCallback]
50 | private void rate_spinbutton_value_changed_cb () {
51 |
52 | rate_changed (rate_spinbutton.value); //Emit signal
53 | }
54 |
55 |
56 | construct {
57 |
58 | _rate = 0.5;
59 | _volume = 0.5;
60 | this.rate_area.draw.connect (draw_rate_cb);
61 | //this.set_tooltip_window (tooltip_window);
62 | this.has_tooltip = true;
63 | this.query_tooltip.connect ((x, y, keyboard_tooltip, tooltip) => {
64 | tooltip.set_custom (tooltip_grid);
65 | return true;
66 | });
67 | }
68 |
69 |
70 |
71 | public ControlMenuButton () {}
72 |
73 |
74 |
75 | public double rate {
76 |
77 | get { return this._rate; }
78 | set {
79 | this._rate = value;
80 | rate_spinbutton.value_changed.disconnect (rate_spinbutton_value_changed_cb);
81 | rate_spinbutton.set_value (rate);
82 | rate_spinbutton.value_changed.connect (rate_spinbutton_value_changed_cb);
83 | GLib.Idle.add ( () => {
84 | this.rate_area.queue_draw ();
85 | set_tooltip ();
86 | return false;
87 | });
88 |
89 | }
90 | }
91 |
92 |
93 |
94 | public double volume {
95 |
96 | get { return this._volume; }
97 | set {
98 | this._volume = value;
99 | double cur_val = this.volume_scale.get_value ();
100 |
101 | if (GLib.Math.fabs (cur_val - value) > 0.001) {
102 |
103 | volume_scale.value_changed.disconnect (volume_scale_value_changed_cb);
104 | volume_scale.set_value (value);
105 | volume_scale.value_changed.connect (volume_scale_value_changed_cb);
106 | }
107 | GLib.Idle.add ( () => {
108 | if (this._volume < 0.05)
109 | volume_button_image.icon_name = ICONS[0];
110 | else if (this._volume < 0.45)
111 | volume_button_image.icon_name = ICONS[1];
112 | else if (this._volume < 0.95)
113 | volume_button_image.icon_name = ICONS[2];
114 | else
115 | volume_button_image.icon_name = ICONS[3];
116 | set_tooltip ();
117 | return false;
118 | });
119 | }
120 | }
121 |
122 |
123 |
124 | private void set_tooltip () {
125 |
126 | string v = "%d %%".printf (
127 | (int) GLib.Math.round (100 * this._volume) );
128 | string r = "%g ×".printf (this._rate);
129 | this.tooltip_volume_label.label = v;
130 | this.tooltip_rate_label.label = r;
131 | }
132 |
133 |
134 |
135 | private bool draw_rate_cb (Cairo.Context cr) {
136 |
137 | uint w;
138 | uint h;
139 | Gdk.RGBA color;
140 | Gdk.RGBA dimmed_color;
141 | Gtk.StyleContext style_context;
142 |
143 | style_context = rate_area.get_style_context ();
144 | color = style_context.get_color ( rate_area.get_state_flags () );
145 | dimmed_color = color;
146 | dimmed_color.alpha *= 0.5;
147 |
148 | if (_rate < 0.5) {
149 | _rate = 0.5;
150 | }
151 |
152 | if (_rate > 1.5) {
153 | _rate = 1.5;
154 | }
155 |
156 | w = rate_area.get_allocated_width ();
157 | h = rate_area.get_allocated_height ();
158 | // coordinates for the center
159 | double px = uint.min (w, h) / 16.0;
160 | double xc = w / 2.0;
161 | double yc = 11 * px;
162 |
163 | double angle_start = 0.95 * GLib.Math.PI;
164 | double angle_end = 2.05 * GLib.Math.PI;
165 | double angle_arrow = (1 + (_rate * 0.5) ) * GLib.Math.PI;
166 |
167 | cr.set_line_width (2 * px);
168 | cr.set_line_cap (Cairo.LineCap.SQUARE);
169 |
170 | Gdk.cairo_set_source_rgba (cr, color);
171 |
172 | cr.arc (xc, yc, 7 * px, angle_start, angle_arrow - 0.65);
173 | cr.stroke ();
174 | int r = (int) GLib.Math.round (_rate * 100);
175 | Gdk.cairo_set_source_rgba (cr,
176 | (r == 100) ? color : dimmed_color);
177 | cr.arc (xc, yc, 7 * px, angle_arrow + 0.65 , angle_end);
178 | cr.stroke ();
179 |
180 | // speedometer arrow
181 | cr.arc (xc, yc, 2 * px, 0, 360);
182 | cr.set_line_width (0);
183 | Gdk.cairo_set_source_rgba (cr, color);
184 | cr.fill ();
185 | // triangle
186 | double x1 = xc + (11 * px * GLib.Math.cos (angle_arrow) );
187 | double y1 = yc + (11 * px * GLib.Math.sin (angle_arrow) );
188 | double x2 = xc + ( 2 * px * GLib.Math.cos (angle_arrow - (GLib.Math.PI / 2.0) ) );
189 | double y2 = yc + ( 2 * px * GLib.Math.sin (angle_arrow - (GLib.Math.PI / 2.0) ) );
190 | double x3 = xc + ( 2 * px * GLib.Math.cos (angle_arrow + (GLib.Math.PI / 2.0) ) );
191 | double y3 = yc + ( 2 * px * GLib.Math.sin (angle_arrow + (GLib.Math.PI / 2.0) ) );
192 | cr.move_to (x1, y1);
193 | cr.line_to (x2, y2);
194 | cr.line_to (x3, y3);
195 | cr.close_path ();
196 | cr.fill ();
197 |
198 | return true;
199 | }
200 | }
201 | }
202 |
--------------------------------------------------------------------------------
/src/ui/shortcuts-window.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | true
6 |
7 |
8 | true
9 | Keyboard Shortcuts
10 | Keyboard Shortcuts
11 |
12 |
13 | true
14 | General
15 |
16 |
17 | true
18 | <ctrl>o
19 | Open an audiobook
20 |
21 |
22 |
23 |
24 | true
25 | Preferences
26 | <ctrl>comma
27 |
28 |
29 |
30 |
31 | true
32 | Keyboard Shortcuts
33 | <ctrl><shift>question
34 |
35 |
36 |
37 |
38 | true
39 | <ctrl>q
40 | Quit
41 |
42 |
43 |
44 |
45 | true
46 | F9
47 | Popup controls
48 |
49 |
50 |
51 |
52 | true
53 | F10
54 | Popup main menu
55 |
56 |
57 |
58 |
59 |
60 |
61 | true
62 | Volume
63 |
64 |
65 | true
66 | 9
67 | Decrease volume
68 |
69 |
70 |
71 |
72 | true
73 | 0
74 | Increase volume
75 |
76 |
77 |
78 |
79 |
80 |
81 | true
82 | Playback
83 |
84 |
85 | true
86 | P space
87 | Play / Pause
88 |
89 |
90 |
91 |
92 | true
93 | <ctrl>Up
94 | Play previous chapter
95 |
96 |
97 |
98 |
99 | true
100 | <ctrl>Down
101 | Play next chapter
102 |
103 |
104 |
105 |
106 | true
107 | <ctrl>Right
108 | Seek forward
109 |
110 |
111 |
112 |
113 | true
114 | <ctrl>Left
115 | Seek backward
116 |
117 |
118 |
119 |
120 |
121 |
122 | true
123 | Playback
124 |
125 |
126 | true
127 | <ctrl><shift>braceleft
128 | Decrease rate (-0.05)
129 |
130 |
131 |
132 |
133 | true
134 | <ctrl><shift>braceright
135 | Increase rate (+0.05)
136 |
137 |
138 |
139 |
140 | true
141 | <ctrl><shift>BackSpace
142 | Reset rate to 1×
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
--------------------------------------------------------------------------------
/src/vapi/mpv.vapi:
--------------------------------------------------------------------------------
1 | [CCode (cheader_filename = "mpv/client.h")]
2 | namespace Mpv {
3 |
4 | [CCode (cname = "enum mpv_error", cprefix = "MPV_ERROR_")]
5 | public enum Error {
6 |
7 | SUCCESS, // No error happened
8 | EVENT_QUEUE_FULL, // The event ringbuffer is full. This means the client is choked, and can't receive any events
9 | NOMEM, // Memory allocation failed
10 | UNINITIALIZED, // The mpv core wasn't configured and initialized yet.
11 | INVALID_PARAMETER, // Generic catch-all error if a parameter is set to an invalid or unsupported value.
12 | OPTION_NOT_FOUND, // Trying to set an option that doesn't exist.
13 | OPTION_FORMAT, // Trying to set an option using an unsupported MPV_FORMAT.
14 | OPTION_ERROR, // Setting the option failed. Typically this happens if the provided option value could not be parsed.
15 | PROPERTY_NOT_FOUND, // The accessed property doesn't exist.
16 | PROPERTY_FORMAT, // Trying to set or get a property using an unsupported MPV_FORMAT.
17 | PROPERTY_UNAVAILABLE, // The property exists, but is not available.
18 | PROPERTY_ERROR, // Error setting or getting a property.
19 | COMMAND, // General error when running a command with mpv_command and similar.
20 | LOADING_FAILED, // Generic error on loading (usually used with mpv_event_end_file.error)
21 | AO_INIT_FAILED, // Initializing the audio output failed
22 | VO_INIT_FAILED, // Initializing the video output failed
23 | NOTHING_TO_PLAY, // There was no audio or video data to play
24 | UNKNOWN_FORMAT, // When trying to load the file, the file format could not be determined, or the file was too broken to open it.
25 | UNSUPPORTED, // Generic error for signaling that certain system requirements are not fulfilled.
26 | NOT_IMPLEMENTED, // The API function which was called is a stub only
27 | GENERIC // Unspecified error
28 | }
29 |
30 | [CCode (cname = "enum mpv_format", has_type_id = false, cprefix = "MPV_FORMAT_")]
31 | public enum Format {
32 |
33 | NONE,
34 | STRING,
35 | OSD_STRING,
36 | FLAG,
37 | INT64,
38 | DOUBLE,
39 | NODE,
40 | NODE_ARRAY,
41 | NODE_MAP,
42 | BYTE_ARRAY
43 | }
44 |
45 | [CCode (cname = "enum mpv_event_id", has_type_id = false, cprefix = "MPV_EVENT_")]
46 | public enum EventID {
47 |
48 | NONE,
49 | SHUTDOWN,
50 | LOG_MESSAGE,
51 | GET_PROPERTY_REPLY,
52 | SET_PROPERTY_REPLY,
53 | COMMAND_REPLY,
54 | START_FILE,
55 | END_FILE,
56 | FILE_LOADED,
57 | IDLE,
58 | TICK,
59 | CLIENT_MESSAGE,
60 | VIDEO_RECONFIG,
61 | AUDIO_RECONFIG,
62 | SEEK,
63 | PLAYBACK_RESTART,
64 | PROPERTY_CHANGE,
65 | QUEUE_OVERFLOW,
66 | HOOK,
67 | //depricated
68 | TRACKS_CHANGED,
69 | TRACK_SWITCHED,
70 | PAUSE,
71 | UNPAUSE,
72 | SCRIPT_INPUT_DISPATCH,
73 | METADATA_UPDATE,
74 | CHAPTER_CHANGE,
75 | }
76 |
77 | [CCode (cname = "enum mpv_end_file_reason", has_type_id = false, cprefix = "MPV_END_FILE_REASON_")]
78 | public enum EndFileReason {
79 |
80 | EOF,
81 | STOP,
82 | QUIT,
83 | ERROR,
84 | REDIRECT,
85 | }
86 |
87 | [CCode (cname = "enum mpv_log_level", has_type_id = false, cprefix = "MPV_LOG_LEVEL_")]
88 | public enum LogLevel {
89 |
90 | NONE, /// "no" - disable absolutely all messages
91 | FATAL, /// "fatal" - critical/aborting errors
92 | ERROR, /// "error" - simple errors
93 | WARN, /// "warn" - possible problems
94 | INFO, /// "info" - informational message
95 | V , /// "v" - noisy informational message
96 | DEBUG, /// "debug" - very noisy technical information
97 | TRACE /// "trace" - extremely noisy
98 | }
99 |
100 | [Compact]
101 | [CCode (cname = "struct mpv_event_property", unref_function ="", free_function ="", copy_function = "")]
102 | public class EventProperty {
103 |
104 | public string name;
105 | public Mpv.Format format;
106 | public unowned T data;
107 | }
108 |
109 | [CCode (cname = "struct mpv_event_log_message")]
110 | public struct EventLogMessage {
111 |
112 | public string prefix;
113 | public string level;
114 | public string text;
115 | public Mpv.LogLevel log_level;
116 | }
117 |
118 | [Compact]
119 | [CCode (cname = "struct mpv_event", unref_function ="", free_function ="", copy_function = "")]
120 | public class Event {
121 |
122 | public Mpv.EventID event_id;
123 | public int error;
124 | public uint64 reply_userdata;
125 | public unowned T data;
126 | }
127 |
128 | [CCode (cname = "struct mpv_event_end_file")]
129 | public struct EventEndFile {
130 |
131 | public Mpv.EndFileReason reason;
132 | public Mpv.Error error;
133 | }
134 |
135 | [CCode (cname = "struct mpv_event_client_message")]
136 | public struct EventClientMessage {
137 |
138 | public int num_args;
139 | public string[] args;
140 | }
141 |
142 | [CCode (cname = "struct mpv_event_hook")]
143 | public struct EventHook {
144 |
145 | public string name;
146 | public uint64 id;
147 | }
148 |
149 | [Compact]
150 | [CCode (cname = "struct mpv_node_list", destroy_function = "", unref_function = "", free_function = "", copy_function = "")]
151 | public class NodeList {
152 |
153 | public int num; // Number of entries. Negative values are not allowed.
154 | [CCode (array_length = false)]
155 | public weak Mpv.Node[] values; // Mpv.Format.NODE_ARRAY: values[N] refers to value of the Nth item
156 | // Mpv.Format.NODE_MAP: values[N] refers to value of the Nth key/value pair
157 | [CCode (array_length = false)]
158 | public string[] keys; // Mpv.Format.NODE_MAP: keys[N] refers to key of the Nth key/value pair.
159 | }
160 |
161 | [Compact]
162 | [CCode (cname = "struct mpv_byte_array", destroy_function = "")]
163 | public class ByteArray {
164 | [CCode (array_length_cname = "size", array_length_type = "size_t")]
165 | public uint8[] data;
166 | }
167 |
168 | [Compact]
169 | [CCode (cname = "struct mpv_node", destroy_function = "", free_function = "mpv_free_node_contents")]
170 | public struct Node {
171 |
172 | [CCode (cname = "u.string")]
173 | public string u_string;
174 | [CCode (cname = "u.flag")]
175 | public int u_flag;
176 | [CCode (cname = "u.int64")]
177 | public int64 u_int64;
178 | [CCode (cname = "u.double_")]
179 | public double u_double;
180 | [CCode (cname = "u.list")]
181 | public weak Mpv.NodeList u_list;
182 | [CCode (cname = "u.ba")]
183 | public Mpv.ByteArray u_ba;
184 | public Mpv.Format format;
185 | //[CCode (cname = "mpv_free_node_contents")]
186 | //public void free_node_contents ();
187 |
188 | }
189 |
190 | [Compact]
191 | [CCode (cname = "mpv_handle", has_type_id = false, ref_function = "", cprefix = "mpv_", unref_function ="", free_function = "mpv_free")]
192 | public class Handle {
193 |
194 | [CCode (cname = "mpv_create")]
195 | public Handle ();
196 |
197 | public string client_name {
198 | [CCode (cname = "mpv_client_name")] get;
199 | }
200 | public Mpv.Error initialize ();
201 | public void terminate_destroy ();
202 | public string load_config_file (string filename);
203 | public int64 get_time_us();
204 | public Mpv.Error set_option (string name, Mpv.Format format);
205 | public Mpv.Error set_option_string (string name, string data);
206 | public Mpv.Error command ([CCode (array_length = false)] string[]? args = null);
207 | public Mpv.Error command_node (Node args, Node result);
208 | public Mpv.Error command_string (string args);
209 | public Mpv.Error command_async (uint64 reply_userdata, [CCode (array_length = false)] string[]? args = null);
210 | public Mpv.Error command_node_async (uint64 reply_userdata, Mpv.Node args);
211 | [CCode (simple_generics = true, has_target = false)]
212 | public Mpv.Error set_property (string name, Mpv.Format format, T data);
213 | public Mpv.Error set_property_string (string name, string data);
214 | [CCode (simple_generics = true, has_target = false)]
215 | public Mpv.Error set_property_async (uint64 reply_userdata, string name, Mpv.Format format, T data);
216 |
217 | [CCode (cname = "mpv_get_property", simple_generics = true, has_target = false)]
218 | public Mpv.Error get_property_double (string name, Mpv.Format format, out double data);
219 |
220 | [CCode (cname = "mpv_get_property", simple_generics = true, has_target = false)]
221 | public Mpv.Error get_property_flag (string name, Mpv.Format format, out int data);
222 |
223 | [CCode (cname = "mpv_get_property", simple_generics = true, has_target = false)]
224 | public Mpv.Error get_property_int64 (string name, Mpv.Format format, out int64 data);
225 |
226 | [CCode (cname = "mpv_get_property", simple_generics = true, has_target = false)]
227 | public Mpv.Error get_property_node (string name, Mpv.Format format, out Mpv.Node data);
228 |
229 | public string get_property_string (string name);
230 | public string get_property_osd_string (string name);
231 | [CCode (simple_generics = true, has_target = false)]
232 | public Mpv.Error get_property_async (uint64 reply_userdata, string name, Mpv.Format format, T data);
233 | public Mpv.Error observe_property (uint64 reply_userdata, string name, Mpv.Format format);
234 | public Mpv.Error unobserve_property (uint64 reply_userdata);
235 | public Mpv.Error request_event (EventID event, bool enable);
236 | public Mpv.Error request_log_messages (string min_level);
237 | public Event? wait_event (double timeout);
238 | public void wakeup ();
239 | public void wait_async_requests ();
240 | public Mpv.Error hook_add (uint64 reply_userdata, string name, int priority);
241 | public Mpv.Error hook_continue (uint64 id);
242 | public void set_wakeup_callback (CallBack callback);
243 | }
244 | [CCode (cname = "cb", simple_generics = true, has_target = true)]
245 | public delegate void CallBack ();
246 |
247 | [CCode (cname = "mpv_free_node_contents")]
248 | public void free_node_contents (Node node);
249 | [CCode (cname = "mpv_event_name")]
250 | public string event_name (EventID event_id);
251 |
252 |
253 | }
--------------------------------------------------------------------------------
/src/ui/chapter-indicator.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | True
7 | False
8 | center
9 | center
10 | 6
11 |
12 |
13 | True
14 | False
15 | True
16 | False
17 |
18 |
19 |
20 | True
21 | False
22 | media-skip-backward-symbolic
23 |
24 |
25 |
26 |
27 | False
28 | True
29 | 0
30 |
31 |
32 |
33 |
34 | True
35 | False
36 | 0
37 | in
38 |
39 |
40 | True
41 | False
42 |
43 |
44 | True
45 | False
46 |
47 |
48 | 82
49 | True
50 | False
51 | vertical
52 |
53 |
54 |
55 |
58 |
59 |
60 | -1
61 |
62 |
63 |
64 |
65 | True
66 | False
67 | vertical
68 |
69 |
70 | True
71 | False
72 |
73 |
74 | True
75 | True
76 | 0
77 |
78 |
79 |
80 |
81 |
82 |
83 | True
84 | False
85 | center
86 | center
87 | 12
88 | 12
89 | vertical
90 |
91 |
92 | True
93 | False
94 |
95 |
96 | False
97 | True
98 | 0
99 |
100 |
101 |
102 |
103 | 1
104 |
105 |
106 |
107 |
108 |
109 |
110 |
113 |
114 |
115 | False
116 | True
117 | 1
118 |
119 |
120 |
121 |
122 | True
123 | False
124 | True
125 | False
126 |
127 |
128 |
129 | True
130 | False
131 | media-skip-forward-symbolic
132 |
133 |
134 |
135 |
136 | False
137 | True
138 | 2
139 |
140 |
141 |
144 |
145 |
146 | False
147 | BalssChapterIndicator
148 |
149 |
150 | True
151 | False
152 | 12
153 | vertical
154 | 6
155 |
156 |
157 |
158 |
159 |
160 | True
161 | False
162 | True
163 | vertical
164 | 6
165 |
166 |
167 | True
168 | False
169 | 6
170 |
171 |
172 |
173 |
174 |
175 | True
176 | False
177 | True
178 | vertical
179 |
180 |
181 |
182 |
183 |
184 | False
185 | True
186 | 1
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 | False
195 | True
196 | 0
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 | False
205 | True
206 | 1
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 | True
217 | False
218 | 6
219 |
220 |
221 | True
222 | False
223 | Total time:
224 | 0
225 |
226 |
227 |
228 |
231 |
232 |
233 | 0
234 | 0
235 |
236 |
237 |
238 |
239 | True
240 | False
241 | 1
242 |
243 |
244 | 1
245 | 0
246 |
247 |
248 |
249 |
250 | True
251 | False
252 |
253 |
254 | 0
255 | 1
256 | 2
257 |
258 |
259 |
260 |
261 |
--------------------------------------------------------------------------------
/src/ui/control-button.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | 0.5
7 | 1.5
8 | 1
9 | 0.05
10 | 10
11 |
12 |
13 | True
14 | False
15 | 6
16 |
17 |
18 | True
19 | False
20 | Volume:
21 | 1
22 |
23 |
24 |
25 |
28 |
29 |
30 | 0
31 | 0
32 |
33 |
34 |
35 |
36 | True
37 | False
38 | Rate:
39 | 1
40 |
41 |
42 |
43 |
46 |
47 |
48 | 0
49 | 1
50 |
51 |
52 |
53 |
54 | True
55 | False
56 | 0
57 |
58 |
59 | 1
60 | 0
61 |
62 |
63 |
64 |
65 | True
66 | False
67 | 0
68 |
69 |
70 | 1
71 | 1
72 |
73 |
74 |
75 |
76 | 1
77 | 0.01
78 | 10
79 |
80 |
81 | False
82 |
83 |
84 | True
85 | False
86 | 12
87 | vertical
88 | 6
89 |
90 |
91 |
92 |
93 |
94 | True
95 | False
96 | 0
97 | 12
98 |
99 |
100 | True
101 | False
102 | vertical
103 |
104 |
105 | True
106 | False
107 | start
108 | center
109 | True
110 | vertical
111 |
112 |
119 |
120 | False
121 | True
122 | 0
123 |
124 |
125 |
126 |
127 | False
128 | True
129 | 0
130 |
131 |
132 |
133 |
134 | False
135 | True
136 | 0
137 |
138 |
139 |
140 |
141 | True
142 | True
143 | True
144 | volume_adjustment
145 | False
146 |
147 |
150 |
151 |
152 | False
153 | True
154 | 1
155 |
156 |
157 |
158 |
159 | False
160 | True
161 | 1
162 |
163 |
164 |
165 |
166 | True
167 | False
168 | True
169 | 0
170 | 12
171 |
172 |
173 | True
174 | False
175 | True
176 | vertical
177 |
178 |
179 | True
180 | False
181 | center
182 | True
183 | vertical
184 |
185 |
192 |
193 | False
194 | True
195 | 0
196 |
197 |
198 |
199 |
200 | False
201 | True
202 | 0
203 |
204 |
205 |
206 |
207 | True
208 | True
209 | 0
210 |
211 |
212 |
213 |
214 | True
215 | True
216 | center
217 | 4
218 | 8
219 | 5
220 | 1.00
221 | 1
222 | media-playback-rate-prefix-symbolic
223 | False
224 | False
225 | number
226 | rate_adjustment
227 | 2
228 | 1
229 |
230 |
231 |
232 | False
233 | True
234 | 1
235 |
236 |
237 |
238 |
239 | False
240 | True
241 | 3
242 |
243 |
244 |
245 |
246 |
247 |
292 |
293 |
294 |
295 |
296 |
297 |
298 |
299 |
--------------------------------------------------------------------------------
/src/mpv-wrapper-backend.vala:
--------------------------------------------------------------------------------
1 | /* mpv-wrapper-backend.vala
2 | *
3 | * Copyright (C) 2018 Nick
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 |
20 |
21 | namespace Balss {
22 |
23 |
24 | public struct Chapter {
25 |
26 | double offset;
27 | double end;
28 | string title;
29 | }
30 |
31 |
32 |
33 | public struct Metadata {
34 |
35 | string title;
36 | string artist;
37 | string album;
38 | string album_artist;
39 | string composer;
40 | string genre;
41 | string date;
42 | string track;
43 | string disc;
44 | }
45 |
46 |
47 |
48 | public class Player : GLib.Object {
49 |
50 |
51 | private Mpv.Handle ctx;
52 |
53 | public signal void volume_changed (double volume);
54 | public signal void mute_changed (bool mute);
55 | public signal void chapter_changed (int index);
56 | public signal void rate_changed (double speed);
57 | public signal void duration_changed (double duration);
58 | public signal void position_updated (double position);
59 | public signal void pause_changed (bool pause);
60 | public signal void metadata_updated ();
61 | public signal void end_of_stream ();
62 | public signal void shutdown ();
63 |
64 | public Metadata metadata;
65 |
66 |
67 |
68 | construct {
69 |
70 | // libmpv: The LC_NUMERIC locale category must be set to "C".
71 | Intl.setlocale (NUMERIC, "C");
72 |
73 | ctx = new Mpv.Handle ();
74 | set_options ();
75 | set_observed_properties ();
76 | ctx.set_wakeup_callback (wakeup_callback);
77 | ctx.initialize ();
78 | metadata = Metadata ();
79 | metadata = { "", "", "", "", "", "", "", "", "" };
80 |
81 | }
82 |
83 |
84 |
85 | public Player () {}
86 |
87 |
88 |
89 | public void load_uri (string uri) {
90 |
91 | string[] cmd = {"loadfile", uri };
92 | check ("1", ctx.command (cmd) );
93 | }
94 |
95 |
96 |
97 | public void destroy_context () {
98 |
99 | ctx.terminate_destroy ();
100 | ctx = null;
101 | }
102 |
103 |
104 |
105 | private void set_options () {
106 |
107 | check ("2", ctx.set_option_string ("video", "no") );
108 | check ("3", ctx.set_option_string ("audio-client-name", APP_ID) );
109 | check ("4", ctx.set_option_string ("config", "no") );
110 | check ("5", ctx.set_option_string ("title", "-") );
111 | //check ("6", ctx.set_option_string ("log-file", "libmpv-log.txt") ); // for debug
112 | }
113 |
114 |
115 |
116 | private void set_observed_properties () {
117 |
118 | check (" 7", ctx.observe_property (0, "duration", Mpv.Format.DOUBLE) );
119 | check (" 8", ctx.observe_property (0, "time-pos", Mpv.Format.DOUBLE) );
120 | check (" 9", ctx.observe_property (0, "ao-volume", Mpv.Format.DOUBLE) );
121 | check ("10", ctx.observe_property (0, "ao-mute", Mpv.Format.FLAG) );
122 | check ("11", ctx.observe_property (0, "pause", Mpv.Format.FLAG) );
123 | check ("12", ctx.observe_property (0, "speed", Mpv.Format.DOUBLE) );
124 | check ("13", ctx.observe_property (0, "metadata", Mpv.Format.NODE) );
125 | check ("14", ctx.observe_property (0, "chapter", Mpv.Format.INT64) );
126 | }
127 |
128 |
129 |
130 | private void wakeup_callback () {
131 |
132 | GLib.Idle.add ( (GLib.SourceFunc) event_handler);
133 | }
134 |
135 |
136 |
137 | private bool event_handler () {
138 |
139 | if (ctx == null)
140 | return false;
141 |
142 | Mpv.Event ev = null;
143 |
144 | while (true) {
145 |
146 | ev = ctx.wait_event (0);
147 |
148 | if (ev.event_id == Mpv.EventID.NONE) { break; }
149 |
150 | if(ev.event_id == Mpv.EventID.PROPERTY_CHANGE) {
151 |
152 | unowned Mpv.EventProperty prop = ( (Mpv.Event) ev).data;
153 |
154 | if ("duration" == prop.name) {
155 | if (prop.format == Mpv.Format.DOUBLE) {
156 | double d = *(double*) ( (Mpv.EventProperty) prop).data;
157 | duration_changed (d); // Emit signal
158 | }
159 | }
160 |
161 | if ("time-pos" == prop.name) {
162 | if (prop.format == Mpv.Format.DOUBLE) {
163 | double pos = *(double*) ( (Mpv.EventProperty) prop).data;
164 | position_updated (pos); // Emit signal
165 | }
166 | }
167 |
168 | if ("pause" == prop.name) {
169 | if (prop.format == Mpv.Format.FLAG) {
170 | int i = *(int*) ( (Mpv.EventProperty)prop).data;
171 | bool p = (i == 0) ? false : true;
172 | pause_changed (p); // Emit signal
173 | }
174 | }
175 |
176 | if ("ao-volume" == prop.name) {
177 | if (prop.format == Mpv.Format.DOUBLE) {
178 | double vol = *(double*) ( (Mpv.EventProperty) prop).data;
179 | volume_changed (vol); // Emit signal
180 | }
181 | }
182 |
183 | if ("ao-mute" == prop.name) {
184 | if (prop.format == Mpv.Format.FLAG) {
185 | int i = *(int*) ( (Mpv.EventProperty) prop).data;
186 | bool m = (i == 0) ? false : true;
187 | mute_changed (m); // Emit signal
188 | }
189 | }
190 |
191 | if ("metadata" == prop.name) {
192 | this.get_metadata ();
193 | metadata_updated (); // Emit signal
194 | }
195 |
196 | if ("chapter" == prop.name) {
197 | if (prop.format == Mpv.Format.INT64) {
198 | int index = (int) *(int64*) ( (Mpv.EventProperty) prop).data;
199 | chapter_changed (index); // Emit signal
200 | }
201 | }
202 |
203 | if ("speed" == prop.name) {
204 | if (prop.format == Mpv.Format.DOUBLE) {
205 | double s = *(double*) ( (Mpv.EventProperty) prop).data;
206 | rate_changed (s); // Emit signal
207 | }
208 | }
209 | }
210 |
211 | if(ev.event_id == Mpv.EventID.END_FILE) {
212 | Mpv.EventEndFile eef = *(Mpv.EventEndFile*) ( (Mpv.Event) ev).data;
213 | if (eef.reason == Mpv.EndFileReason.EOF) {
214 | end_of_stream (); // Emit signal
215 | }
216 | if (eef.reason == Mpv.EndFileReason.ERROR) {
217 | debug ("Playback was terminated abnormally. ERROR ID %d",
218 | eef.error );
219 | }
220 | break;
221 | }
222 |
223 | if(ev.event_id == Mpv.EventID.IDLE) {
224 | //FIXME
225 | break;
226 | }
227 |
228 | if(ev.event_id == Mpv.EventID.SHUTDOWN) {
229 | shutdown (); // Emit signal
230 | break;
231 | }
232 |
233 | }
234 | return false;
235 | }
236 |
237 |
238 |
239 | public void set_previous_chapter () {
240 |
241 | string[] command = {"add", "chapter", "-1"};
242 | check ("15",
243 | ctx.command_async (Mpv.EventID.COMMAND_REPLY, command) );
244 | }
245 |
246 |
247 |
248 | public void set_next_chapter () {
249 |
250 | string[] command = {"add", "chapter", "1"};
251 | check ("16",
252 | ctx.command_async (Mpv.EventID.COMMAND_REPLY, command) );
253 | }
254 |
255 |
256 |
257 |
258 | public int get_chapter_count () {
259 |
260 | int64 i;
261 | check ("17",
262 | ctx.get_property_int64 ("chapters",
263 | Mpv.Format.INT64,
264 | out i) );
265 |
266 | return (int) i;
267 | }
268 |
269 |
270 |
271 | public int get_chapter () {
272 |
273 | int64 i;
274 | check ("19",
275 | ctx.get_property_int64 ("chapter",
276 | Mpv.Format.INT64,
277 | out i) );
278 |
279 | return (int) i;
280 | }
281 |
282 |
283 |
284 | public void set_chapter (int index) {
285 |
286 | int64? i = (int64) index;
287 | if (get_chapter() != index) {
288 | check ("20",
289 | ctx.set_property_async (Mpv.EventID.NONE,
290 | "chapter",
291 | Mpv.Format.INT64,
292 | i) );
293 | }
294 | }
295 |
296 |
297 |
298 | public double get_position () {
299 |
300 | double p;
301 | check ("21",
302 | ctx.get_property_double ("time-pos",
303 | Mpv.Format.DOUBLE,
304 | out p) );
305 | return (double) p;
306 | }
307 |
308 |
309 |
310 | public void set_position (double? p) {
311 |
312 | check ("22",
313 | ctx.set_property_async (Mpv.EventID.NONE,
314 | "time-pos",
315 | Mpv.Format.DOUBLE,
316 | p) );
317 | }
318 |
319 |
320 |
321 | public double get_duration () {
322 |
323 | double d;
324 | check ("23",
325 | ctx.get_property_double ("duration",
326 | Mpv.Format.DOUBLE,
327 | out d) );
328 | return (double) d;
329 | }
330 |
331 |
332 |
333 | public bool get_pause () {
334 |
335 | int i;
336 | check ("24",
337 | ctx.get_property_flag ("pause",
338 | Mpv.Format.FLAG,
339 | out i) );
340 | return (bool) (i == 0) ? false : true;
341 | }
342 |
343 |
344 |
345 | public void play () {
346 |
347 | set_pause (false);
348 | }
349 |
350 |
351 |
352 | public void pause () {
353 |
354 | set_pause (true);
355 | }
356 |
357 |
358 |
359 | private void set_pause (bool pause) {
360 |
361 | int? i = (pause == false) ? 0 : 1;
362 | check ("25",
363 | ctx.set_property_async (Mpv.EventID.NONE,
364 | "pause",
365 | Mpv.Format.FLAG,
366 | i) );
367 | }
368 |
369 |
370 |
371 | public double get_volume () {
372 |
373 | double v;
374 | check ("26",
375 | ctx.get_property_double ("ao-volume",
376 | Mpv.Format.DOUBLE,
377 | out v) );
378 |
379 | return (double) v;
380 | }
381 |
382 |
383 |
384 | public void set_volume (double? vol) {
385 |
386 | check ("27",
387 | ctx.set_property_async (Mpv.EventID.NONE,
388 | "ao-volume",
389 | Mpv.Format.DOUBLE,
390 | vol) );
391 | }
392 |
393 |
394 |
395 | public bool get_mute () {
396 |
397 | int i;
398 | check ("28",
399 | ctx.get_property_flag ("ao-mute",
400 | Mpv.Format.FLAG,
401 | out i) );
402 | return (bool) (i == 0) ? false : true;
403 | }
404 |
405 |
406 |
407 | public void set_mute (bool? mute) {
408 |
409 | int m = (mute == false) ? 0 : 1;
410 | check ("29",
411 | ctx.set_property_async (Mpv.EventID.NONE,
412 | "ao-mute",
413 | Mpv.Format.FLAG,
414 | m) );
415 | }
416 |
417 |
418 |
419 | public bool get_soft_mute () {
420 |
421 | int i;
422 | check ("30",
423 | ctx.get_property_flag ("mute",
424 | Mpv.Format.FLAG,
425 | out i) );
426 | return (bool) (i == 0) ? false : true;
427 | }
428 |
429 |
430 |
431 | public void set_soft_mute (bool? mute) {
432 |
433 | int m = (mute == false) ? 0 : 1;
434 | check ("31",
435 | ctx.set_property_async (Mpv.EventID.NONE,
436 | "mute",
437 | Mpv.Format.FLAG,
438 | m) );
439 | }
440 |
441 |
442 |
443 | public double get_rate () {
444 |
445 | double s;
446 | check ("32",
447 | ctx.get_property_double ("speed",
448 | Mpv.Format.DOUBLE,
449 | out s) );
450 | return (double) s;
451 | }
452 |
453 |
454 |
455 | public void set_rate (double? speed) {
456 |
457 | check ("33",
458 | ctx.set_property_async (Mpv.EventID.NONE,
459 | "speed",
460 | Mpv.Format.DOUBLE,
461 | speed) );
462 | }
463 |
464 |
465 |
466 | private void get_metadata () {
467 |
468 | Mpv.Node node;
469 | string key = "";
470 |
471 | check ("34",
472 | ctx.get_property_node ("metadata", Mpv.Format.NODE, out node) );
473 |
474 | if (node.format == Mpv.Format.NODE_MAP) {
475 | for (int i = 0; i < node.u_list.num; i++) {
476 | if (node.u_list.values[i].format == Mpv.Format.STRING) {
477 |
478 | key = node.u_list.keys[i];
479 |
480 | if ("title" == key) {
481 | metadata.title = (string) node.u_list.values[i].u_string ?? "";
482 | }
483 | if ("artist" == key) {
484 | metadata.artist = (string) node.u_list.values[i].u_string ?? "";
485 | }
486 | if ("album" == key) {
487 | metadata.album = (string) node.u_list.values[i].u_string ?? "";
488 | }
489 | if ("album_artist" == key) {
490 | metadata.album_artist = (string) node.u_list.values[i].u_string ?? "";
491 | }
492 | if ("composer" == key) {
493 | metadata.composer = (string) node.u_list.values[i].u_string ?? "";
494 | }
495 | if ("genre" == key) {
496 | metadata.genre = (string) node.u_list.values[i].u_string ?? "";
497 | }
498 | if ("date" == key) {
499 | metadata.date = (string) node.u_list.values[i].u_string ?? "";
500 | }
501 | if ("track" == key) {
502 | metadata.track = (string) node.u_list.values[i].u_string ?? "";
503 | }
504 | if ("disc" == key) {
505 | metadata.disc = (string) node.u_list.values[i].u_string ?? "";
506 | }
507 | }
508 | }
509 | }
510 | check ("35", ctx.set_option_string ("title", metadata.title) );
511 | }
512 |
513 |
514 |
515 | public GLib.List? get_chapter_list () {
516 |
517 | Mpv.Node node;
518 | string title = "";
519 | string t = "";
520 | double offset = 0;
521 | double o = 0;
522 | int count = 0;
523 | bool ready = false;
524 | var list = new GLib.List ();
525 | check ("34",
526 | ctx.get_property_node ("chapter-list", Mpv.Format.NODE, out node) );
527 |
528 | if(node.format == Mpv.Format.NODE_ARRAY) {
529 | count = node.u_list.num;
530 | for (int i = 0; i < count; i++) {
531 | if (node.u_list.values[i].format == Mpv.Format.NODE_MAP) {
532 | for (int n = 0; n < node.u_list.values[i].u_list.num; n++) {
533 | if ("title" == node.u_list.values[i].u_list.keys[n]) {
534 | if (node.u_list.values[i].u_list.values[n].format == Mpv.Format.STRING) {
535 | t = (string) node.u_list.values[i].u_list.values[n].u_string;
536 | }
537 | } else if ("time" == node.u_list.values[i].u_list.keys[n]) {
538 | if (node.u_list.values[i].u_list.values[n].format == Mpv.Format.DOUBLE) {
539 | o = (double) node.u_list.values[i].u_list.values[n].u_double;
540 | }
541 | }
542 | }
543 | if (ready) {
544 |
545 | Chapter chapter = Chapter ();
546 | chapter.title = title.strip () ;
547 | chapter.offset = offset;
548 | chapter.end = o;
549 | list.append (chapter);
550 | } else {
551 |
552 | ready = true;
553 | }
554 |
555 | title = t;
556 | offset = o;
557 |
558 | if (count - 1 == i) {
559 |
560 | Chapter chapter = Chapter ();
561 | chapter.title = t;
562 | chapter.offset = o;
563 | chapter.end = get_duration ();
564 | list.append (chapter);
565 | }
566 | }
567 | }
568 | }
569 | return list;
570 | }
571 |
572 |
573 |
574 | private void check (string id, Mpv.Error er) {
575 |
576 | if (er != Mpv.Error.SUCCESS)
577 | debug ("ERROR CODE: %d, id: %s", er, id );
578 | }
579 |
580 | }
581 | }
582 |
--------------------------------------------------------------------------------
/src/ui/window.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
116 |
117 | False
118 | Balss
119 | 540
120 | 600
121 |
122 |
123 | True
124 | False
125 | slide-left-right
126 | True
127 |
128 |
129 | True
130 | False
131 | vertical
132 |
133 |
134 | True
135 | False
136 | vertical
137 |
138 |
139 |
140 |
143 |
144 |
145 | True
146 | True
147 | 0
148 |
149 |
150 |
151 |
152 | True
153 | False
154 | vertical
155 |
156 |
157 |
158 |
159 |
160 | False
161 | True
162 | 1
163 |
164 |
165 |
166 |
167 | main
168 |
169 |
170 |
171 |
172 | True
173 | False
174 | vertical
175 |
176 |
177 | True
178 | False
179 | vertical
180 |
181 |
182 |
183 |
184 |
185 | True
186 | True
187 | 0
188 |
189 |
190 |
191 |
192 | prefs
193 | 1
194 |
195 |
196 |
197 |
198 |
199 |
387 |
388 |
389 |
390 |
--------------------------------------------------------------------------------
/src/window.vala:
--------------------------------------------------------------------------------
1 | /* window.vala
2 | *
3 | * Copyright (C) 2018 Nick
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 |
20 |
21 | namespace Balss {
22 |
23 | [GtkTemplate (ui = "/com/gitlab/nvlgit/Balss/window.ui")]
24 | public class PlayerWindow : Gtk.ApplicationWindow {
25 |
26 | public signal void new_notification (string title, string body);
27 |
28 | [GtkChild] private Gtk.Stack win_stack;
29 | [GtkChild] private Gtk.Stack header_stack;
30 | [GtkChild] private Gtk.HeaderBar header_main;
31 | [GtkChild] private Gtk.Button button_play;
32 | [GtkChild] private Gtk.Button seek_forward_button;
33 | [GtkChild] private Gtk.Button seek_backward_button;
34 | [GtkChild] private Gtk.Image pause_image;
35 | [GtkChild] private Gtk.Image play_image;
36 | [GtkChild] private Gtk.Box chapter_list_placeholder_box;
37 | [GtkChild] private Gtk.Box bottom_placeholder_box;
38 | [GtkChild] private Gtk.Box prefs_placeholder_box;
39 | [GtkChild] private Gtk.ModelButton open_menu_item;
40 | [GtkChild] private Gtk.ModelButton prefs_menu_item;
41 | [GtkChild] private Gtk.ModelButton shotcuts_menu_item;
42 | [GtkChild] private Gtk.ModelButton quit_menu_item;
43 | [GtkChild] private Gtk.Box control_button_placeholder_box;
44 |
45 | private Player? player;
46 | private GLib.List? list;
47 | private ChapterListBox? lb;
48 | private ChapterIndicator indicator;
49 | private ControlMenuButton control_button;
50 | private PrefsPage prefs_page;
51 |
52 | private double duration;
53 | private bool audiobook;
54 | private string basename;
55 | private int64 rounded_seconds;
56 | private double current_chapter_offset;
57 | private double current_chapter_duration;
58 | private bool speed_was_setted;
59 | private bool was_eos;
60 | private bool initial_volume_was_setted;
61 | private bool seek_needed;
62 | private bool chapter_changed_init;
63 |
64 |
65 |
66 | construct {
67 |
68 | this.duration = 0;
69 | this.rounded_seconds = -1;
70 | this.current_chapter_offset = 0;
71 | this.current_chapter_duration = 0;
72 | this.audiobook = false;
73 | temp = false;
74 |
75 | this.speed_was_setted = false;
76 | this.seek_needed = false;
77 | this.was_eos = false;
78 | this.initial_volume_was_setted = false;
79 | this.chapter_changed_init =false;
80 |
81 | indicator = new ChapterIndicator ();
82 | indicator.button_clicked.connect (indicator_button_clicked);
83 | bottom_placeholder_box.pack_start (this.indicator, true, true, 0);
84 | prefs_page = new PrefsPage ();
85 | prefs_page.update_gtk_theme ();
86 | prefs_placeholder_box.pack_start (this.prefs_page, true, true, 0);
87 | control_button = new ControlMenuButton ();
88 | control_button_placeholder_box.pack_start (this.control_button, true, true, 0);
89 | control_button.volume_changed.connect(control_button_volume_changed_cb);
90 | control_button.rate_changed.connect(control_button_rate_changed_cb);
91 | }
92 |
93 | private bool keypress_cb (Gdk.EventKey event) {
94 |
95 | if (event.type != Gdk.EventType.KEY_PRESS)
96 | return false;
97 |
98 | switch (event.hardware_keycode) {
99 |
100 | case 32: //Gdk.Key.o
101 | if (event.state == Gdk.ModifierType.CONTROL_MASK ||
102 | event.state == Gdk.ModifierType.CONTROL_MASK + Gdk.ModifierType.LOCK_MASK)
103 | open_menu_item.clicked ();
104 | break;
105 |
106 | case 59: //Gdk.Key.comma
107 | if (event.state == Gdk.ModifierType.CONTROL_MASK ||
108 | event.state == Gdk.ModifierType.CONTROL_MASK + Gdk.ModifierType.LOCK_MASK)
109 | prefs_menu_item.clicked ();
110 | else
111 | return false;
112 | break;
113 |
114 | case 61: //Gdk.Key.question
115 | if (event.state == Gdk.ModifierType.CONTROL_MASK + Gdk.ModifierType.SHIFT_MASK ||
116 | event.state == Gdk.ModifierType.CONTROL_MASK + Gdk.ModifierType.SHIFT_MASK + Gdk.ModifierType.LOCK_MASK)
117 | shotcuts_menu_item.clicked ();
118 | else
119 | return false;
120 | break;
121 |
122 | case 24: //Gdk.Key.q:
123 | if (event.state == Gdk.ModifierType.CONTROL_MASK ||
124 | event.state == Gdk.ModifierType.CONTROL_MASK + Gdk.ModifierType.LOCK_MASK) {
125 | debug ("Ctrl + Q");
126 | quit_menu_item.clicked ();
127 | } else {
128 | return false;
129 | }
130 | break;
131 |
132 | case 75: // "F9"
133 | if (event.state == 0)
134 | control_button.clicked ();
135 | else
136 | return false;
137 | break;
138 |
139 | case 18: // "9"
140 | if (event.state == 0)
141 | decrease_volume ();
142 | else
143 | return false;
144 | break;
145 |
146 | case 19: // "0"
147 | if (event.state == 0)
148 | increase_volume ();
149 | else
150 | return false;
151 | break;
152 |
153 | case 33: // "P"
154 | case 65: // "Space"
155 | if (event.state == 0)
156 | button_play_clicked_cb ();
157 | else
158 | return false;
159 | break;
160 |
161 | case 80: //Gdk.Key.KP_Up:
162 | case 111: //Gdk.Key.Up:
163 | if (event.state == Gdk.ModifierType.CONTROL_MASK ||
164 | event.state == Gdk.ModifierType.CONTROL_MASK + Gdk.ModifierType.LOCK_MASK) {
165 | debug ("Ctrl + UP");
166 | player.set_previous_chapter ();
167 | } else {
168 | return false;
169 | }
170 | break;
171 |
172 | case 88: //Gdk.Key.KP_Down:
173 | case 116: //Gdk.Key.Down:
174 | if (event.state == Gdk.ModifierType.CONTROL_MASK ||
175 | event.state == Gdk.ModifierType.CONTROL_MASK + Gdk.ModifierType.LOCK_MASK) {
176 | debug ("Ctrl + Down");
177 | player.set_next_chapter ();
178 | } else {
179 | return false;
180 | }
181 | break;
182 |
183 | case 34: // "{"
184 | if (event.state == Gdk.ModifierType.CONTROL_MASK + Gdk.ModifierType.SHIFT_MASK ||
185 | event.state == Gdk.ModifierType.CONTROL_MASK + Gdk.ModifierType.SHIFT_MASK + Gdk.ModifierType.LOCK_MASK)
186 | decrease_rate ();
187 | else
188 | return false;
189 | break;
190 |
191 | case 35: // "}"
192 | if (event.state == Gdk.ModifierType.CONTROL_MASK + Gdk.ModifierType.SHIFT_MASK ||
193 | event.state == Gdk.ModifierType.CONTROL_MASK + Gdk.ModifierType.SHIFT_MASK + Gdk.ModifierType.LOCK_MASK)
194 | increase_rate ();
195 | else
196 | return false;
197 | break;
198 |
199 | case 22: //Gdk.Key.BackSpace:
200 | if (event.state == Gdk.ModifierType.CONTROL_MASK + Gdk.ModifierType.SHIFT_MASK ||
201 | event.state == Gdk.ModifierType.CONTROL_MASK + Gdk.ModifierType.SHIFT_MASK + Gdk.ModifierType.LOCK_MASK)
202 | player.set_rate(1.0);
203 | else
204 | return false;
205 | break;
206 |
207 | default:
208 | return false;
209 | }
210 | debug ("Gtk.EventController");
211 | return true;
212 | }
213 |
214 | public PlayerWindow (Gtk.Application app) {
215 | GLib.Object (application: app);
216 |
217 | this.set_default_size (App.preferences.window_width, App.preferences.window_height);
218 | this.delete_event.connect (window_delete_event_cb);
219 | this.key_press_event.connect (keypress_cb);
220 |
221 | if (App.preferences.play_last) { // when startup without argument
222 | if (App.preferences.last_uri.length > 6) {
223 | this.seek_needed = true;
224 | open (App.preferences.last_uri);
225 | }
226 | }
227 | }
228 |
229 |
230 |
231 | public void open (string uri) {
232 |
233 | if (App.preferences.play_last) {
234 | if (uri == App.preferences.last_uri) {
235 | this.seek_needed = true;
236 | }
237 | }
238 | if (App.preferences.last_uri != uri) {
239 | App.preferences.last_uri = uri;
240 | }
241 | this.basename = File.new_for_uri (uri).get_basename ();
242 | player_check_reinit ();
243 | activate_buttons ();
244 | this.speed_was_setted = false;
245 | player.load_uri (uri);
246 | }
247 |
248 |
249 |
250 |
251 | private void activate_buttons () {
252 |
253 | button_play.sensitive = true;
254 | seek_forward_button.sensitive = true;
255 | seek_backward_button.sensitive = true;
256 | control_button_placeholder_box.visible = true;
257 | player.rate_changed.disconnect (player_rate_changed_cb);
258 | control_button.rate = App.preferences.playback_rate;
259 | player.rate_changed.connect (player_rate_changed_cb);
260 |
261 |
262 | }
263 | public void show_page (string page_name) {
264 |
265 | win_stack.set_visible_child_name (page_name);
266 | header_stack.set_visible_child_name (page_name);
267 | }
268 |
269 |
270 |
271 | private void clear_ui_interface () {
272 |
273 | if (lb != null) {
274 | ( (Gtk. Container) chapter_list_placeholder_box).remove (lb);
275 | lb = null;
276 | }
277 | this.indicator.clear ();
278 | }
279 |
280 |
281 |
282 | private void connect_player_signals () {
283 |
284 | player.position_updated.connect (player_position_updated_cb);
285 | player.duration_changed.connect (player_duration_changed_cb);
286 | player.rate_changed.connect (player_rate_changed_cb);
287 | player.metadata_updated.connect (player_metadata_updated_cb);
288 | player.volume_changed.connect (player_volume_changed_cb);
289 | player.chapter_changed.connect (player_chapter_changed_cb);
290 | player.pause_changed.connect (player_pause_changed_cb);
291 | player.end_of_stream.connect (player_end_of_stream_cb);
292 |
293 | player.mute_changed.connect ( (m) => { //FIXME Needed?
294 | //debug ("Mute: %s\n", (m == false) ? "Unmuted" : "Muted" );
295 | });
296 | }
297 |
298 |
299 |
300 | private void show_chapter_list () {
301 |
302 | this.lb = new ChapterListBox ();
303 | chapter_list_placeholder_box.pack_end (lb, true, true, 0);
304 | this.lb.hexpand = true;
305 | int n = 0;
306 |
307 | this.list.foreach ( (c) => {
308 |
309 | var row = new ChapterRow ();
310 | n++;
311 | row.number = n.to_string ();
312 | row.title = c.title;
313 | row.duration = to_hhmmss (c.end - c.offset);
314 | this.lb.add_row (row);
315 | });
316 | this.lb.chapter_row_activated.connect (lb_chapter_row_activated_cb);
317 | this.lb.seek_changed.connect (seekbar_value_changed_cb);
318 | }
319 |
320 |
321 |
322 | private void lb_chapter_row_activated_cb (int index){
323 |
324 | if (this.player == null) return;
325 |
326 | this.player.set_chapter (index);
327 |
328 | if (this.player.get_pause () )
329 | this.player.play ();
330 | }
331 |
332 |
333 |
334 | private void show_one_list_item () {
335 |
336 | this.lb = new ChapterListBox ();
337 | chapter_list_placeholder_box.pack_end (lb, true, true, 0);
338 | this.lb.hexpand = true;
339 |
340 | var row = new ChapterRow ();
341 | row.number = "";
342 | row.title = this.basename;
343 | row.duration = to_hhmmss (this.duration);
344 | this.lb.add_row (row);
345 | this.lb.seek_changed.connect (seekbar_value_changed_cb);
346 | this.lb.mark_row_at_index (0);
347 | this.lb.set_range (0, this.duration );
348 | }
349 |
350 |
351 |
352 | private void indicator_button_clicked (int direction){
353 |
354 | if (this.player == null) return;
355 |
356 | if (direction < 0)
357 | player.set_previous_chapter ();
358 | else
359 | player.set_next_chapter ();
360 | }
361 |
362 |
363 |
364 | [GtkCallback] private void show_main_page_cb () {
365 |
366 | show_page ("main");
367 | }
368 |
369 |
370 |
371 | private void control_button_rate_changed_cb (double val) {
372 |
373 | if (this.player == null) return;
374 | player.set_rate ( (double) val);
375 | }
376 |
377 |
378 |
379 | [GtkCallback] private void button_play_clicked_cb () {
380 |
381 | if (this.player == null) return;
382 |
383 | if (player.get_pause () == true) {
384 | player.play ();
385 | } else {
386 | player.pause ();
387 | }
388 | }
389 |
390 |
391 |
392 | [GtkCallback] private void seek_forward_button_clicked_cb () {
393 |
394 | if (this.player == null) return;
395 |
396 | double pos = player.get_position () + 5;
397 | if (pos < this.duration)
398 | this.player.set_position (pos);
399 | }
400 |
401 |
402 |
403 | [GtkCallback] private void seek_backward_button_clicked_cb () {
404 |
405 | if (this.player == null) return;
406 |
407 | double pos = player.get_position ();
408 | if (pos < 5)
409 | pos = 0;
410 | else
411 | pos = pos -5;
412 | this.player.set_position (pos);
413 | }
414 |
415 |
416 |
417 | private void control_button_volume_changed_cb (double val) {
418 |
419 | player.set_volume (val * 100);
420 | }
421 |
422 |
423 |
424 | private void seekbar_value_changed_cb (double val) {
425 |
426 | if (this.player == null) return;
427 |
428 | player.set_position (val);
429 | }
430 |
431 |
432 |
433 | private void player_check_reinit () {
434 |
435 | if (this.player != null) {
436 | this.player.destroy_context ();
437 | this.player = null;
438 | }
439 | this.player = new Player ();
440 | connect_player_signals ();
441 | }
442 |
443 | private bool window_delete_event_cb (Gtk.Widget widget,
444 | Gdk.EventAny event) {
445 | on_close_window ();
446 | return false;
447 | }
448 |
449 |
450 |
451 | public void on_close_window () {
452 |
453 | int w, h;
454 | player.pause ();
455 | App.preferences.last_position = player.get_position ();
456 | this.get_size (out w, out h);
457 | App.preferences.window_width = w;
458 | App.preferences.window_height = h;
459 | player.destroy_context ();
460 | player = null;
461 | }
462 |
463 |
464 | private void increase_rate () {
465 |
466 | if (this.player == null) return;
467 | var rate = this.player.get_rate () + 0.05;
468 | if (rate > 1.5) rate = 1.5;
469 | this.player.set_rate (rate);
470 | }
471 |
472 |
473 | private void decrease_rate () {
474 |
475 | if (this.player == null) return;
476 | var rate = this.player.get_rate () - 0.05;
477 | if (rate < 0.5) rate = 0.5;
478 | this.player.set_rate (rate);
479 | }
480 |
481 | private void increase_volume () {
482 |
483 | if (this.player == null) return;
484 | var val = this.player.get_volume () + 5;
485 |
486 | if (val > 100)
487 | val = 100;
488 | this.player.set_volume (val);
489 | }
490 |
491 |
492 | private void decrease_volume () {
493 |
494 | if (this.player == null) return;
495 | var val = this.player.get_volume () - 5;
496 | if (val < 0.0)
497 | val = 0;
498 | this.player.set_volume (val);
499 | }
500 |
501 |
502 | private void player_pause_changed_cb (bool p) {
503 |
504 | if (p == true) {
505 | pause_image.visible = false;
506 | play_image.visible = true;
507 | } else {
508 | play_image.visible = false;
509 | pause_image.visible = true;
510 | }
511 | debug ("pause_changed");
512 | }
513 |
514 |
515 |
516 | private void player_duration_changed_cb (double dur) {
517 |
518 | clear_ui_interface ();
519 | this.duration = dur;
520 |
521 | if (0 < player.get_chapter_count () )
522 | this.audiobook = true;
523 | else
524 | this.audiobook = false;
525 |
526 | if (this.audiobook) {
527 | this.list = new GLib.List ();
528 | this.list = player.get_chapter_list();
529 | show_chapter_list ();
530 | } else {
531 | show_one_list_item ();
532 | }
533 | //this.lb.set_range (0, this.duration);
534 | debug ("duration_changed");
535 |
536 | }
537 |
538 | private bool temp;
539 |
540 | private void player_position_updated_cb (double pos) {
541 |
542 | if (!temp) {
543 | debug ("position_changed");
544 | temp = true;
545 | }
546 | check_and_apply_initial_stuffs ();
547 |
548 | int64 rounded_pos = GLib.Math.llround (pos);
549 | if (this.rounded_seconds != rounded_pos) {
550 |
551 | set_lb_position (pos);
552 | set_progress_fraction (pos);
553 | this.rounded_seconds = rounded_pos;
554 | }
555 |
556 | /* GLib.Idle.add ( () => {
557 |
558 | set_lb_position (pos);
559 | set_progress_fraction (pos);
560 | //this.rounded_seconds = rounded_pos;
561 | return false;
562 | });
563 | */
564 | }
565 |
566 |
567 |
568 | private void check_and_apply_initial_stuffs () {
569 |
570 | if (this.was_eos) {
571 | player.pause ();
572 | this.was_eos = false;
573 | }
574 | if (!this.speed_was_setted) {
575 | player.set_rate ( (double) App.preferences.playback_rate);
576 | this.speed_was_setted = true;
577 | }
578 | if (!initial_volume_was_setted) {
579 | this.control_button.volume = this.player.get_volume () / 100;
580 | initial_volume_was_setted = true;
581 | }
582 | if (this.seek_needed) {
583 | //this.player.set_soft_mute (true);
584 | player.pause ();
585 | player.set_position (App.preferences.last_position);
586 | this.seek_needed = false;
587 | player.play();
588 | //this.player.set_soft_mute (false);
589 | }
590 | }
591 |
592 |
593 |
594 | private void player_chapter_changed_cb (int i) {
595 |
596 | debug ("chapter_changed");
597 |
598 | if (i < 0) { //FIXME sametimes -1
599 | debug ("Negative chapter index: %d", i);
600 | i = 0;
601 | }
602 |
603 | int count = player.get_chapter_count ();
604 |
605 | this.current_chapter_offset = this.list.nth_data (i).offset;
606 | this.current_chapter_duration =
607 | this.list.nth_data (i).end - this.current_chapter_offset;
608 |
609 | this.lb.mark_row_at_index (i);
610 | this.lb.set_range (this.list.nth_data (i).offset,
611 | this.list.nth_data (i).end );
612 | this.chapter_changed_init = true;//!!!!!!!
613 |
614 | this.indicator.set_info (i+1, count);
615 |
616 | bool notify = App.preferences.show_notifications;
617 | if (notify) {
618 | string body = "%d. %s".printf (i+1, this.list.nth_data (i).title);
619 | new_notification (player.metadata.title, body); // emit signal
620 | }
621 | }
622 |
623 |
624 |
625 | private void player_volume_changed_cb (double val) {
626 |
627 | debug ("volume changed");
628 | this.control_button.volume = val / 100;
629 | }
630 |
631 |
632 |
633 | private void player_rate_changed_cb (double val) {
634 |
635 | debug ("rate_changed");
636 | this.control_button.rate = val;
637 | //label = "%g×".printf (val);
638 | }
639 |
640 |
641 |
642 | private void player_metadata_updated_cb () {
643 |
644 | debug ("metadata_changed");
645 | this.title = this.header_main.title =
646 | (player.metadata.title != "") ? player.metadata.title : this.basename;
647 |
648 | string artist =
649 | (player.metadata.artist != "") ? player.metadata.artist : "";
650 |
651 | string genre =
652 | (player.metadata.genre != "") ? player.metadata.genre : "";
653 |
654 | string year = "";
655 | if (player.metadata.date.length > 3) {
656 | year = player.metadata.date.substring (0, 4);
657 | }
658 | this.header_main.subtitle = "%s%s%s".printf (artist,
659 | genre != "" ? " • " + genre : "",
660 | year != "" ? " • " + year : "");
661 | }
662 |
663 |
664 |
665 | private void player_end_of_stream_cb () {
666 |
667 | debug ("end of stream");
668 | was_eos = true;
669 | player_check_reinit ();
670 | this.player.load_uri (App.preferences.last_uri);
671 | }
672 |
673 |
674 |
675 | private void set_lb_position (double pos) {
676 |
677 | if (this.lb == null) return;
678 |
679 | this.lb.set_position (pos);
680 |
681 | if (audiobook) {
682 | this.lb.set_time (to_hhmmss (pos - this.current_chapter_offset) );
683 | } else {
684 | this.lb.set_time (to_hhmmss (pos) );
685 | }
686 | }
687 |
688 | private void set_progress_fraction (double pos) {
689 |
690 | if (this.duration == 0) return;
691 |
692 | if (pos == 0) {
693 | //var percent
694 | this.indicator.set_fraction (0);
695 | } else {
696 | var val = pos / duration;
697 | this.indicator.set_fraction (val);
698 | }
699 | int persent = calculate_percentage (pos, duration);
700 | this.indicator.set_tooltip (persent,
701 | to_hhmmss (pos),
702 | to_hhmmss(this.duration) );
703 | }
704 |
705 | private int calculate_percentage (double p, double w) {
706 |
707 | int ret = 0;
708 | if (p > 0 && w > 0)
709 | ret = (int) GLib.Math.round (100 * p / w);
710 | return ret;
711 | }
712 |
713 |
714 |
715 | private string to_hhmmss (double seconds) {
716 |
717 | string ret = "0:00";
718 | int hh = 0, mm = 0, ss = 0;
719 |
720 | if (seconds < 0.5)
721 | return ret;
722 |
723 | int64 secs = (int64) GLib.Math.llround (seconds);
724 |
725 | hh = (int) secs / (60 * 60);
726 | mm = (int) secs / 60 - (hh * 60);
727 | ss = (int) secs % 60;
728 |
729 | if (hh > 0)
730 | ret = "%d:%02d:%02d".printf (hh, mm, ss);
731 | else
732 | ret = "%d:%02d".printf (mm, ss);
733 |
734 | return ret;
735 | }
736 |
737 | /*************** TODO *****************/
738 |
739 | public double get_rate () {
740 |
741 | return player.get_rate ();
742 | }
743 |
744 | public double get_volume () {
745 |
746 | return player.get_volume ();
747 | }
748 |
749 | public void set_volume (double vol) {
750 |
751 | player.set_volume (vol);
752 | }
753 |
754 |
755 |
756 |
757 | }
758 | }
759 |
--------------------------------------------------------------------------------
/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 |
635 | Copyright (C)
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 | Copyright (C)
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 |
--------------------------------------------------------------------------------