├── .github
├── CODEOWNERS
├── CODE_OF_CONDUCT.md
├── ISSUE_TEMPLATE
│ ├── bug_report.yml
│ └── feature_request.yml
├── PULL_REQUEST_TEMPLATE
│ ├── bug_fix.md
│ └── new_feature.md
└── workflows
│ ├── aur_update.yml
│ ├── aur_update_git.yml
│ └── github_metrics.yml
├── .gitignore
├── .gitmodules
├── ACKNOWLEDGMENTS.md
├── AUTHORS.md
├── CHANGELOG.md
├── CITATION.cff
├── CONTRIBUTING.md
├── CONTRIBUTORS.md
├── LICENSE
├── README.md
├── SUPPORT.md
├── assets
├── css
│ └── cavasik.css
├── fork_profile.png
├── icons
│ ├── devel
│ │ └── io.github.fsobolev.Cavasik-Workspace.svg
│ ├── io.github.TheWisker.Cavasik-symbolic.svg
│ ├── io.github.TheWisker.Cavasik.png
│ ├── io.github.TheWisker.Cavasik.svg
│ └── meson.build
├── io.github.TheWisker.Cavasik.desktop.in
├── io.github.TheWisker.Cavasik.gschema.xml
├── io.github.TheWisker.Cavasik.metainfo.xml.in
├── meson.build
├── metrics
│ ├── base.svg
│ ├── contributors.svg
│ ├── languages.svg
│ └── licenses.svg
├── profile.png
└── screenshots
│ ├── bars.png
│ ├── bars_circle.png
│ ├── direction_top.png
│ ├── levels.png
│ ├── main.png
│ ├── mirror_column.png
│ ├── mirror_inverted.png
│ ├── mirror_normal.png
│ ├── mirror_overlapping.png
│ ├── particles.png
│ ├── spine.png
│ ├── wave_circle.png
│ └── waves.png
├── aur
├── cavasik-git
│ ├── .SRCINFO
│ └── PKGBUILD
└── cavasik
│ ├── .SRCINFO
│ └── PKGBUILD
├── cavasik.doap
├── io.github.TheWisker.Cavasik.json
├── lang
├── CONTRIBUTING.md
├── CREDITS.json
├── LINGUAS
├── POTFILES.in
├── cavasik.pot
├── de.po
├── es.po
├── fr.po
├── it.po
├── jp.po
├── meson.build
├── nl.po
├── pt_BR.po
└── ru.po
├── meson.build
├── src
├── __init__.py
├── cava.py
├── cavasik.gresource.xml
├── cavasik.in
├── dbus.py
├── draw_functions.py
├── drawing_area.py
├── main.py
├── meson.build
├── preferences_window.py
├── settings.py
├── settings_import_export.py
├── shortcuts.py
├── shortcuts_dialog.ui
├── translation_credits.py.in
└── window.py
└── utils
└── flatpak-pip-generator.py
/.github/CODEOWNERS:
--------------------------------------------------------------------------------
1 | # CODEOWNERS
2 |
3 | # Specifies the owner of each file in this branch using a structure similar to .gitignore files
4 |
5 | # This owner will be the default owner for everything in the repo.
6 | # Unless a later match takes precedence, @TheWisker will be requested for review when someone opens a pull request.
7 | * @TheWisker
--------------------------------------------------------------------------------
/.github/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 |
Cavasik
2 |
7 | Audio visualizer based on CAVA
8 |
9 | Contributor Covenant Code of Conduct
10 |
11 | ## Our Pledge
12 |
13 | We as members, contributors, and leaders pledge to make participation in our
14 | community a harassment-free experience for everyone, regardless of age, body
15 | size, visible or invisible disability, ethnicity, sex characteristics, gender
16 | identity and expression, level of experience, education, socio-economic status,
17 | nationality, personal appearance, race, religion, or sexual identity
18 | and orientation.
19 |
20 | We pledge to act and interact in ways that contribute to an open, welcoming,
21 | diverse, inclusive, and healthy community.
22 |
23 | ## Our Standards
24 |
25 | Examples of behavior that contributes to a positive environment for our
26 | community include:
27 |
28 | * Demonstrating empathy and kindness toward other people
29 | * Being respectful of differing opinions, viewpoints, and experiences
30 | * Giving and gracefully accepting constructive feedback
31 | * Accepting responsibility and apologizing to those affected by our mistakes,
32 | and learning from the experience
33 | * Focusing on what is best not just for us as individuals, but for the
34 | overall community
35 |
36 | Examples of unacceptable behavior include:
37 |
38 | * The use of sexualized language or imagery, and sexual attention or
39 | advances of any kind
40 | * Trolling, insulting or derogatory comments, and personal or political attacks
41 | * Public or private harassment
42 | * Publishing others' private information, such as a physical or email
43 | address, without their explicit permission
44 | * Other conduct which could reasonably be considered inappropriate in a
45 | professional setting
46 |
47 | ## Enforcement Responsibilities
48 |
49 | Community leaders are responsible for clarifying and enforcing our standards of
50 | acceptable behavior and will take appropriate and fair corrective action in
51 | response to any behavior that they deem inappropriate, threatening, offensive,
52 | or harmful.
53 |
54 | Community leaders have the right and responsibility to remove, edit, or reject
55 | comments, commits, code, wiki edits, issues, and other contributions that are
56 | not aligned to this Code of Conduct, and will communicate reasons for moderation
57 | decisions when appropriate.
58 |
59 | ## Scope
60 |
61 | This Code of Conduct applies within all community spaces, and also applies when
62 | an individual is officially representing the community in public spaces.
63 | Examples of representing our community include using an official e-mail address,
64 | posting via an official social media account, or acting as an appointed
65 | representative at an online or offline event.
66 |
67 | ## Enforcement
68 |
69 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
70 | reported to the community leaders responsible for enforcement at
71 | TheWisker@protonmail.com.
72 | All complaints will be reviewed and investigated promptly and fairly.
73 |
74 | All community leaders are obligated to respect the privacy and security of the
75 | reporter of any incident.
76 |
77 | ## Enforcement Guidelines
78 |
79 | Community leaders will follow these Community Impact Guidelines in determining
80 | the consequences for any action they deem in violation of this Code of Conduct:
81 |
82 | ### 1. Correction
83 |
84 | **Community Impact**: Use of inappropriate language or other behavior deemed
85 | unprofessional or unwelcome in the community.
86 |
87 | **Consequence**: A private, written warning from community leaders, providing
88 | clarity around the nature of the violation and an explanation of why the
89 | behavior was inappropriate. A public apology may be requested.
90 |
91 | ### 2. Warning
92 |
93 | **Community Impact**: A violation through a single incident or series
94 | of actions.
95 |
96 | **Consequence**: A warning with consequences for continued behavior. No
97 | interaction with the people involved, including unsolicited interaction with
98 | those enforcing the Code of Conduct, for a specified period of time. This
99 | includes avoiding interactions in community spaces as well as external channels
100 | like social media. Violating these terms may lead to a temporary or
101 | permanent ban.
102 |
103 | ### 3. Temporary Ban
104 |
105 | **Community Impact**: A serious violation of community standards, including
106 | sustained inappropriate behavior.
107 |
108 | **Consequence**: A temporary ban from any sort of interaction or public
109 | communication with the community for a specified period of time. No public or
110 | private interaction with the people involved, including unsolicited interaction
111 | with those enforcing the Code of Conduct, is allowed during this period.
112 | Violating these terms may lead to a permanent ban.
113 |
114 | ### 4. Permanent Ban
115 |
116 | **Community Impact**: Demonstrating a pattern of violation of community
117 | standards, including sustained inappropriate behavior, harassment of an
118 | individual, or aggression toward or disparagement of classes of individuals.
119 |
120 | **Consequence**: A permanent ban from any sort of public interaction within
121 | the community.
122 |
123 | ## Attribution
124 |
125 | This Code of Conduct is adapted from the [Contributor Covenant][homepage],
126 | version 2.0, available at
127 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
128 |
129 | Community Impact Guidelines were inspired by [Mozilla's code of conduct
130 | enforcement ladder](https://github.com/mozilla/diversity).
131 |
132 | [homepage]: https://www.contributor-covenant.org
133 |
134 | For answers to common questions about this code of conduct, see the FAQ at
135 | https://www.contributor-covenant.org/faq. Translations are available at
136 | https://www.contributor-covenant.org/translations.
137 |
138 | Author
139 |
144 | TheWisker
145 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.yml:
--------------------------------------------------------------------------------
1 | name: Bug Report
2 | description: File a bug report
3 | title: "[BUG] Title"
4 | labels: ["bug"]
5 | assignees:
6 | - TheWisker
7 | body:
8 | - type: markdown
9 | attributes:
10 | value: |
11 | # Cavasik Bug Report
12 | Thanks for taking the time to fill out this bug report!
13 | - type: textarea
14 | id: explanation
15 | attributes:
16 | label: Explanation
17 | description: Clear and concise description of the bug
18 | placeholder: There is a bug in ... when you click ...
19 | validations:
20 | required: true
21 | - type: textarea
22 | id: steps
23 | attributes:
24 | label: Steps to reproduce
25 | description: Steps to reproduce the issue
26 | placeholder: |
27 | 1. Go to '...'
28 | 2. Click on '....'
29 | 3. Scroll down to '....'
30 | 4. See error
31 | - type: dropdown
32 | id: version
33 | attributes:
34 | label: Cavasik Version
35 | description: What version of our software are you running?
36 | options:
37 | - v1.0.0
38 | validations:
39 | required: true
40 | - type: input
41 | id: distro
42 | attributes:
43 | label: Linux Distro
44 | placeholder: ex. Arch
45 | validations:
46 | required: true
47 | - type: textarea
48 | id: context
49 | attributes:
50 | label: Additional Context
51 | description: Add any other context about the problem here
52 | - type: textarea
53 | id: logs
54 | attributes:
55 | label: Relevant log output
56 | description: Please copy and paste any relevant log output.
57 | render: shell
58 | - type: checkboxes
59 | id: terms
60 | attributes:
61 | label: Code of Conduct
62 | description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/TheWisker/Cavasik/blob/master/.github/CODE_OF_CONDUCT.md)
63 | options:
64 | - label: I agree to follow this project's Code of Conduct
65 | required: true
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.yml:
--------------------------------------------------------------------------------
1 | name: Feature Request
2 | description: Suggest an idea for this project
3 | title: "[Enhancement] Title"
4 | labels: ["enhancement"]
5 | assignees:
6 | - TheWisker
7 | body:
8 | - type: markdown
9 | attributes:
10 | value: |
11 | # Cavasik Feature Request
12 | Thanks for taking the time to fill out this feature request!
13 | - type: textarea
14 | id: explanation
15 | attributes:
16 | label: Explanation
17 | description: Clear and concise description of the feature request
18 | placeholder: I would like that ... is added and ... is changed because ...
19 | validations:
20 | required: true
21 | - type: textarea
22 | id: behavior
23 | attributes:
24 | label: Expected Behavior
25 | description: Clear and concise description of what you expected to happen
26 | placeholder: I would like that when I ... ... happens
27 | validations:
28 | required: true
29 | - type: textarea
30 | id: alternatives
31 | attributes:
32 | label: Alternative Solutions
33 | description: Clear and concise description of any alternative solutions or features you've considered
34 | placeholder: I have also considered ... and it can also be achieved with ...
35 | - type: textarea
36 | id: context
37 | attributes:
38 | label: Additional Context
39 | description: Add any other context about the feature request here
40 | - type: checkboxes
41 | id: terms
42 | attributes:
43 | label: Code of Conduct
44 | description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/TheWisker/Cavasik/blob/master/.github/CODE_OF_CONDUCT.md)
45 | options:
46 | - label: I agree to follow this project's Code of Conduct
47 | required: true
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE/bug_fix.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug fix
3 | about: Create a pull request about a bug fix
4 | title: "[BUG] Title"
5 | labels: bug
6 | assignees: TheWisker
7 |
8 | ---
9 |
10 | # Cavasik Pull Request
11 |
12 | This is a **template** for a **bug fix** pull request for **Cavasik**.
13 |
14 | ## Explanation
15 |
16 | 1. Include a **summary** of the changes and the **related issue**.
17 | 2. Include relevant **motivation** and **context**.
18 | 3. List any **dependency** changes that are required for this pull request.
19 | 4. List exactly what it **fixes**.
20 |
21 | ## Checklist
22 |
23 | Check all **true** statements about this pull request:
24 |
25 | - [ ] It is a **non-breaking** change which fixes an issue.
26 | - [ ] It is a **breaking** change that causes existing functionality to not work as expected.
27 | - [ ] This change requires a **documentation update**.
28 | - [ ] It follows the **style** guidelines of this project.
29 | - [ ] The code is **commented**, particularly in hard-to-understand areas.
30 | - [ ] **Self-reviews** of the code have been performed.
31 | - [ ] The changes generate **no new warnings**.
32 | - [ ] It has been **tested** for stability.
33 |
34 | ### Thank you for contributing!
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE/new_feature.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: New feature
3 | about: Create a pull request about a new feature
4 | title: "[Enhancement] Title"
5 | labels: enhancement
6 | assignees: TheWisker
7 |
8 | ---
9 |
10 | # Cavasik Pull Request
11 |
12 | This is a **template** for a **new feature** pull request for **Cavasik**.
13 |
14 | ## Explanation
15 |
16 | 1. Include a **summary** of the changes and the **additions** made.
17 | 2. Include relevant **motivation** and **context**.
18 | 3. List any **dependency** changes that are required for this pull request.
19 | 4. List exactly what it **changes**: **adds** and **removes**.
20 |
21 | ## Checklist
22 |
23 | Check all **true** statements about this pull request:
24 |
25 | - [ ] It is a **non-breaking** change.
26 | - [ ] It is a **breaking** change that causes existing functionality to not work as expected.
27 | - [ ] This change requires a **documentation update**.
28 | - [ ] It follows the **style** guidelines of this project.
29 | - [ ] The code is **commented**, particularly in hard-to-understand areas.
30 | - [ ] **Self-reviews** of the code have been performed.
31 | - [ ] The changes generate **no new warnings**.
32 | - [ ] It has been **tested** for stability.
33 |
34 | ### Thank you for contributing!
--------------------------------------------------------------------------------
/.github/workflows/aur_update.yml:
--------------------------------------------------------------------------------
1 | name: AUR Update
2 | run-name: AUR package update by ${{ github.actor }} release
3 | on:
4 | release:
5 | types: [published]
6 | permissions:
7 | contents: write
8 | concurrency:
9 | group: "aur"
10 | cancel-in-progress: true
11 | jobs:
12 | aur-update:
13 | environment:
14 | name: aur-packages
15 | runs-on: ubuntu-latest
16 | container:
17 | image: archlinux
18 | options: --privileged
19 | steps:
20 | - name: AUR Update
21 | uses: TheWisker/aur-update@master
22 | with:
23 | aur_key: ${{ secrets.AUR_KEY }}
24 | username: TheWisker
25 | email: TheWisker@protonmail.com
--------------------------------------------------------------------------------
/.github/workflows/aur_update_git.yml:
--------------------------------------------------------------------------------
1 | name: AUR Update Git
2 | run-name: AUR package update by ${{ github.actor }} push
3 | on:
4 | push:
5 | branches:
6 | - 'master'
7 | permissions:
8 | contents: write
9 | concurrency:
10 | group: "aur"
11 | cancel-in-progress: false
12 | jobs:
13 | aur-update-git:
14 | environment:
15 | name: aur-packages
16 | runs-on: ubuntu-latest
17 | container:
18 | image: archlinux
19 | options: --privileged
20 | steps:
21 | - name: AUR Update Git
22 | uses: TheWisker/aur-update-git@master
23 | with:
24 | aur_key: ${{ secrets.AUR_KEY }}
25 | username: TheWisker
26 | email: TheWisker@protonmail.com
--------------------------------------------------------------------------------
/.github/workflows/github_metrics.yml:
--------------------------------------------------------------------------------
1 | name: GitHub Metrics
2 | run-name: Updating ${{ github.event.repository.name }} metrics
3 | on:
4 | schedule:
5 | - cron: '0 0 * * *'
6 | workflow_dispatch:
7 | permissions:
8 | contents: write
9 | concurrency:
10 | group: "metrics"
11 | cancel-in-progress: true
12 | jobs:
13 | github-metrics:
14 | runs-on: ubuntu-latest
15 | environment:
16 | name: repo-metrics
17 | steps:
18 | - name: Configure Git Credentials
19 | shell: bash
20 | env:
21 | username: ${{ github.repository_owner }}
22 | email: TheWisker@protonmail.com
23 | repo_name: ${{ github.event.repository.name }}
24 | run: |
25 | git config --global user.name $username
26 | git config --global user.email $email
27 | git config --global init.defaultBranch master
28 | git config --global --add safe.directory /__w/$repo_name/$repo_name
29 |
30 | echo "Configured Git Credentials"
31 |
32 | - name: Base Metrics
33 | uses: TheWisker/GitHubMetrics@master
34 | with:
35 | token: ${{ secrets.METRICS_TOKEN }}
36 | user: ${{ github.repository_owner }}
37 | repo: ${{ github.event.repository.name }}
38 | #committer_token:
39 | #committer_branch:
40 | #committer_message:
41 | #committer_gist:
42 | filename: base.*
43 | #markdown:
44 | #markdown_cache:
45 | output_action: none
46 | output_condition: data-changed
47 | optimize: css, xml, svg
48 | #setup_community_templates:
49 | template: repository
50 | #query:
51 | #extras_css:
52 | #extras_js:
53 | #github_api_rest:
54 | #github_api_graphql:
55 | config_timezone: Europe/Madrid
56 | #config_order:
57 | #config_twemoji:
58 | config_gemoji: yes
59 | #config_octicon:
60 | config_display: regular
61 | config_animations: yes
62 | config_base64: yes
63 | #config_padding:
64 | config_output: auto
65 | #config_presets:
66 | retries: 5
67 | retries_delay: 600
68 | retries_output_action: 5
69 | retries_delay_output_action: 300
70 | clean_workflows: failure
71 | #delay:
72 | #quota_required_rest:
73 | #quota_required_graphql:
74 | #quota_required_search:
75 | notice_releases: yes
76 | use_prebuilt_image: yes
77 | #plugins_errors_fatal:
78 | debug: no
79 | verify: no
80 | #debug_flags:
81 | #debug_print:
82 | #dryrun:
83 | experimental_features: --optimize-svg
84 | #use_mocked_data
85 | base: header, activity, community, repositories
86 | base_indepth: yes
87 | #base_hireable:
88 | #base_skip:
89 | repositories: 200
90 | repositories_batch: 100
91 | repositories_forks: yes
92 | repositories_affiliations: owner, collaborator, organization_member
93 | #repositories_skipped:
94 | #users_ignored:
95 | #commits_authoring:
96 | plugin_traffic: yes
97 | #plugin_traffic_skipped:
98 | plugin_lines: yes
99 | #plugin_lines_sections:
100 | #plugin_lines_repositories_limit:
101 | plugin_lines_history_limit: 1
102 |
103 | - name: Languages Metrics
104 | uses: TheWisker/GitHubMetrics@master
105 | with:
106 | token: ${{ secrets.METRICS_TOKEN }}
107 | user: ${{ github.repository_owner }}
108 | repo: ${{ github.event.repository.name }}
109 | #committer_token:
110 | #committer_branch:
111 | #committer_message:
112 | #committer_gist:
113 | filename: languages.*
114 | #markdown:
115 | #markdown_cache:
116 | output_action: none
117 | output_condition: data-changed
118 | optimize: css, xml, svg
119 | #setup_community_templates:
120 | template: repository
121 | #query:
122 | #extras_css:
123 | #extras_js:
124 | #github_api_rest:
125 | #github_api_graphql:
126 | config_timezone: Europe/Madrid
127 | #config_order:
128 | #config_twemoji:
129 | config_gemoji: yes
130 | #config_octicon:
131 | config_display: regular
132 | config_animations: yes
133 | config_base64: yes
134 | #config_padding:
135 | config_output: auto
136 | #config_presets:
137 | retries: 5
138 | retries_delay: 600
139 | retries_output_action: 5
140 | retries_delay_output_action: 300
141 | clean_workflows: failure
142 | #delay:
143 | #quota_required_rest:
144 | #quota_required_graphql:
145 | #quota_required_search:
146 | notice_releases: yes
147 | use_prebuilt_image: yes
148 | #plugins_errors_fatal:
149 | debug: no
150 | verify: no
151 | #debug_flags:
152 | #debug_print:
153 | #dryrun:
154 | experimental_features: --optimize-svg
155 | #use_mocked_data
156 | base: ""
157 | plugin_languages: yes
158 | #plugin_languages_ignored:
159 | #plugin_languages_skipped:
160 | plugin_languages_limit: 0
161 | #plugin_languages_threshold:
162 | #plugin_languages_other:
163 | #plugin_languages_colors:
164 | #plugin_languages_aliases:
165 | plugin_languages_sections: most-used
166 | plugin_languages_details: bytes-size, percentage, lines
167 | plugin_languages_indepth: yes
168 | #plugin_languages_indepth_custom:
169 | plugin_languages_analysis_timeout: 60
170 | plugin_languages_analysis_timeout_repositories: 15
171 | #plugin_languages_categories:
172 | #plugin_languages_recent_categories:
173 | plugin_languages_recent_load: 1000
174 | plugin_languages_recent_days: 365
175 |
176 | - name: Contributors Metrics
177 | uses: TheWisker/GitHubMetrics@master
178 | with:
179 | token: ${{ secrets.METRICS_TOKEN }}
180 | user: ${{ github.repository_owner }}
181 | repo: ${{ github.event.repository.name }}
182 | #committer_token:
183 | #committer_branch:
184 | #committer_message:
185 | #committer_gist:
186 | filename: contributors.*
187 | #markdown:
188 | #markdown_cache:
189 | output_action: none
190 | output_condition: data-changed
191 | optimize: css, xml, svg
192 | #setup_community_templates:
193 | template: repository
194 | #query:
195 | #extras_css:
196 | #extras_js:
197 | #github_api_rest:
198 | #github_api_graphql:
199 | config_timezone: Europe/Madrid
200 | #config_order:
201 | #config_twemoji:
202 | config_gemoji: yes
203 | #config_octicon:
204 | config_display: regular
205 | config_animations: yes
206 | config_base64: yes
207 | #config_padding:
208 | config_output: auto
209 | #config_presets:
210 | retries: 5
211 | retries_delay: 600
212 | retries_output_action: 5
213 | retries_delay_output_action: 300
214 | clean_workflows: failure
215 | #delay:
216 | #quota_required_rest:
217 | #quota_required_graphql:
218 | #quota_required_search:
219 | notice_releases: yes
220 | use_prebuilt_image: yes
221 | #plugins_errors_fatal:
222 | debug: no
223 | verify: no
224 | #debug_flags:
225 | #debug_print:
226 | #dryrun:
227 | experimental_features: --optimize-svg
228 | #use_mocked_data
229 | base: ""
230 | plugin_contributors: yes
231 | #plugin_contributors_base:
232 | #plugin_contributors_head:
233 | #plugin_contributors_ignored:
234 | plugin_contributors_contributions: yes
235 | #plugin_contributors_sections:
236 | #plugin_contributors_categories:
237 |
238 | - name: Licenses Metrics
239 | uses: TheWisker/GitHubMetrics@master
240 | with:
241 | token: ${{ secrets.METRICS_TOKEN }}
242 | user: ${{ github.repository_owner }}
243 | repo: ${{ github.event.repository.name }}
244 | #committer_token:
245 | #committer_branch:
246 | #committer_message:
247 | #committer_gist:
248 | filename: licenses.*
249 | #markdown:
250 | #markdown_cache:
251 | output_action: none
252 | output_condition: data-changed
253 | optimize: css, xml, svg
254 | #setup_community_templates:
255 | template: repository
256 | #query:
257 | #extras_css:
258 | #extras_js:
259 | #github_api_rest:
260 | #github_api_graphql:
261 | config_timezone: Europe/Madrid
262 | #config_order:
263 | #config_twemoji:
264 | config_gemoji: yes
265 | #config_octicon:
266 | config_display: regular
267 | config_animations: yes
268 | config_base64: yes
269 | #config_padding:
270 | config_output: auto
271 | #config_presets:
272 | retries: 5
273 | retries_delay: 600
274 | retries_output_action: 5
275 | retries_delay_output_action: 300
276 | clean_workflows: failure
277 | #delay:
278 | #quota_required_rest:
279 | #quota_required_graphql:
280 | #quota_required_search:
281 | notice_releases: yes
282 | use_prebuilt_image: yes
283 | #plugins_errors_fatal:
284 | debug: no
285 | verify: no
286 | #debug_flags:
287 | #debug_print:
288 | #dryrun:
289 | experimental_features: --optimize-svg
290 | #use_mocked_data:
291 | base: ""
292 | plugin_licenses: yes
293 | plugin_licenses_setup: npm install
294 | #plugin_licenses_ratio:
295 | plugin_licenses_legal: yes
296 |
297 | - name: Checkout
298 | uses: actions/checkout@v3
299 | with:
300 | ref: master
301 |
302 | - name: Flush Metrics
303 | shell: bash
304 | run: |
305 | rm -fr ./assets/metrics
306 | mkdir -p ./assets/metrics
307 | cp -f /metrics_renders/* ./assets/metrics
308 |
309 | echo "Flushed Metrics"
310 |
311 | - name: Update Repository
312 | shell: bash
313 | run: |
314 | git add .
315 | git commit -m "Metrics Update"
316 | git push
317 |
318 | echo "Updated Repository"
319 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.rsuser
8 | *.suo
9 | *.user
10 | *.userosscache
11 | *.sln.docstates
12 |
13 | # User-specific files (MonoDevelop/Xamarin Studio)
14 | *.userprefs
15 |
16 | # Build results
17 | [Dd]ebug/
18 | [Dd]ebugPublic/
19 | [Rr]elease/
20 | [Rr]eleases/
21 | x64/
22 | x86/
23 | bld/
24 | [Bb]in/
25 | [Oo]bj/
26 | [Ll]og/
27 |
28 | # Visual Studio 2015/2017 cache/options directory
29 | .vs/
30 | .vscode/
31 | # Uncomment if you have tasks that create the project's static files in wwwroot
32 | #wwwroot/
33 |
34 | # Visual Studio 2017 auto generated files
35 | Generated\ Files/
36 |
37 | # MSTest test Results
38 | [Tt]est[Rr]esult*/
39 | [Bb]uild[Ll]og.*
40 |
41 | # NUNIT
42 | *.VisualState.xml
43 | TestResult.xml
44 |
45 | # Build Results of an ATL Project
46 | [Dd]ebugPS/
47 | [Rr]eleasePS/
48 | dlldata.c
49 |
50 | # Benchmark Results
51 | BenchmarkDotNet.Artifacts/
52 |
53 | # .NET Core
54 | project.lock.json
55 | project.fragment.lock.json
56 | artifacts/
57 |
58 | # StyleCop
59 | StyleCopReport.xml
60 |
61 | # Files built by Visual Studio
62 | *_i.c
63 | *_p.c
64 | *_h.h
65 | *.ilk
66 | *.meta
67 | *.obj
68 | *.iobj
69 | *.pch
70 | *.pdb
71 | *.ipdb
72 | *.pgc
73 | *.pgd
74 | *.rsp
75 | *.sbr
76 | *.tlb
77 | *.tli
78 | *.tlh
79 | *.tmp
80 | *.tmp_proj
81 | *_wpftmp.csproj
82 | *.log
83 | *.vspscc
84 | *.vssscc
85 | .builds
86 | *.pidb
87 | *.svclog
88 | *.scc
89 |
90 | # Chutzpah Test files
91 | _Chutzpah*
92 |
93 | # Visual C++ cache files
94 | ipch/
95 | *.aps
96 | *.ncb
97 | *.opendb
98 | *.opensdf
99 | *.sdf
100 | *.cachefile
101 | *.VC.db
102 | *.VC.VC.opendb
103 |
104 | # Visual Studio profiler
105 | *.psess
106 | *.vsp
107 | *.vspx
108 | *.sap
109 |
110 | # Visual Studio Trace Files
111 | *.e2e
112 |
113 | # TFS 2012 Local Workspace
114 | $tf/
115 |
116 | # Guidance Automation Toolkit
117 | *.gpState
118 |
119 | # ReSharper is a .NET coding add-in
120 | _ReSharper*/
121 | *.[Rr]e[Ss]harper
122 | *.DotSettings.user
123 |
124 | # JustCode is a .NET coding add-in
125 | .JustCode
126 |
127 | # TeamCity is a build add-in
128 | _TeamCity*
129 |
130 | # DotCover is a Code Coverage Tool
131 | *.dotCover
132 |
133 | # AxoCover is a Code Coverage Tool
134 | .axoCover/*
135 | !.axoCover/settings.json
136 |
137 | # Visual Studio code coverage results
138 | *.coverage
139 | *.coveragexml
140 |
141 | # NCrunch
142 | _NCrunch_*
143 | .*crunch*.local.xml
144 | nCrunchTemp_*
145 |
146 | # MightyMoose
147 | *.mm.*
148 | AutoTest.Net/
149 |
150 | # Web workbench (sass)
151 | .sass-cache/
152 |
153 | # Installshield output folder
154 | [Ee]xpress/
155 |
156 | # DocProject is a documentation generator add-in
157 | DocProject/buildhelp/
158 | DocProject/Help/*.HxT
159 | DocProject/Help/*.HxC
160 | DocProject/Help/*.hhc
161 | DocProject/Help/*.hhk
162 | DocProject/Help/*.hhp
163 | DocProject/Help/Html2
164 | DocProject/Help/html
165 |
166 | # Click-Once directory
167 | publish/
168 |
169 | # Publish Web Output
170 | *.[Pp]ublish.xml
171 | *.azurePubxml
172 | # Note: Comment the next line if you want to checkin your web deploy settings,
173 | # but database connection strings (with potential passwords) will be unencrypted
174 | *.pubxml
175 | *.publishproj
176 |
177 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
178 | # checkin your Azure Web App publish settings, but sensitive information contained
179 | # in these scripts will be unencrypted
180 | PublishScripts/
181 |
182 | # NuGet Packages
183 | *.nupkg
184 | # The packages folder can be ignored because of Package Restore
185 | **/[Pp]ackages/*
186 | # except build/, which is used as an MSBuild target.
187 | !**/[Pp]ackages/build/
188 | # Uncomment if necessary however generally it will be regenerated when needed
189 | #!**/[Pp]ackages/repositories.config
190 | # NuGet v3's project.json files produces more ignorable files
191 | *.nuget.props
192 | *.nuget.targets
193 |
194 | # Microsoft Azure Build Output
195 | csx/
196 | *.build.csdef
197 |
198 | # Microsoft Azure Emulator
199 | ecf/
200 | rcf/
201 |
202 | # Windows Store app package directories and files
203 | AppPackages/
204 | BundleArtifacts/
205 | Package.StoreAssociation.xml
206 | _pkginfo.txt
207 | *.appx
208 |
209 | # Visual Studio cache files
210 | # files ending in .cache can be ignored
211 | *.[Cc]ache
212 | # but keep track of directories ending in .cache
213 | !*.[Cc]ache/
214 |
215 | # Others
216 | ClientBin/
217 | ~$*
218 | *~
219 | *.dbmdl
220 | *.dbproj.schemaview
221 | *.jfm
222 | *.pfx
223 | *.publishsettings
224 | orleans.codegen.cs
225 |
226 | # Including strong name files can present a security risk
227 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
228 | #*.snk
229 |
230 | # Since there are multiple workflows, uncomment next line to ignore bower_components
231 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
232 | #bower_components/
233 |
234 | # RIA/Silverlight projects
235 | Generated_Code/
236 |
237 | # Backup & report files from converting an old project file
238 | # to a newer Visual Studio version. Backup files are not needed,
239 | # because we have git ;-)
240 | _UpgradeReport_Files/
241 | Backup*/
242 | UpgradeLog*.XML
243 | UpgradeLog*.htm
244 | ServiceFabricBackup/
245 | *.rptproj.bak
246 |
247 | # SQL Server files
248 | *.mdf
249 | *.ldf
250 | *.ndf
251 |
252 | # Business Intelligence projects
253 | *.rdl.data
254 | *.bim.layout
255 | *.bim_*.settings
256 | *.rptproj.rsuser
257 |
258 | # Microsoft Fakes
259 | FakesAssemblies/
260 |
261 | # GhostDoc plugin setting file
262 | *.GhostDoc.xml
263 |
264 | # Node.js Tools for Visual Studio
265 | .ntvs_analysis.dat
266 | node_modules/
267 |
268 | # Visual Studio 6 build log
269 | *.plg
270 |
271 | # Visual Studio 6 workspace options file
272 | *.opt
273 |
274 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
275 | *.vbw
276 |
277 | # Visual Studio LightSwitch build output
278 | **/*.HTMLClient/GeneratedArtifacts
279 | **/*.DesktopClient/GeneratedArtifacts
280 | **/*.DesktopClient/ModelManifest.xml
281 | **/*.Server/GeneratedArtifacts
282 | **/*.Server/ModelManifest.xml
283 | _Pvt_Extensions
284 |
285 | # Paket dependency manager
286 | .paket/paket.exe
287 | paket-files/
288 |
289 | # FAKE - F# Make
290 | .fake/
291 |
292 | # JetBrains Rider
293 | .idea/
294 | *.sln.iml
295 |
296 | # CodeRush personal settings
297 | .cr/personal
298 |
299 | # Python Tools for Visual Studio (PTVS)
300 | __pycache__/
301 | *.pyc
302 |
303 | # Cake - Uncomment if you are using it
304 | # tools/**
305 | # !tools/packages.config
306 |
307 | # Tabs Studio
308 | *.tss
309 |
310 | # Telerik's JustMock configuration file
311 | *.jmconfig
312 |
313 | # BizTalk build output
314 | *.btp.cs
315 | *.btm.cs
316 | *.odx.cs
317 | *.xsd.cs
318 |
319 | # OpenCover UI analysis results
320 | OpenCover/
321 |
322 | # Azure Stream Analytics local run output
323 | ASALocalRun/
324 |
325 | # MSBuild Binary and Structured Log
326 | *.binlog
327 |
328 | # NVidia Nsight GPU debugger configuration file
329 | *.nvuser
330 |
331 | # MFractors (Xamarin productivity tool) working folder
332 | .mfractor/
333 |
334 | # Local History for Visual Studio
335 | .localhistory/
336 |
337 | #Intellij
338 | .idea/
339 |
340 | #Resources
341 | *.Designer.cs
342 |
343 | # Flatpak Builder
344 | .flatpak-builder/
345 | .flatpak/
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "shared-modules"]
2 | path = shared-modules
3 | url = https://github.com/flathub/shared-modules.git
4 |
--------------------------------------------------------------------------------
/ACKNOWLEDGMENTS.md:
--------------------------------------------------------------------------------
1 | Cavasik
2 |
7 | Audio visualizer based on CAVA
8 |
9 | Acknowledgments
10 |
11 | This page gives credit to the underlying projects in which Cavasik is based upon.
12 |
13 | ## Acknowledgment Index
14 |
15 | - [Cavalier][cavalier-hook]
16 | - [Cava][cava-hook]
17 | - [Gtk][gtk-hook]
18 | - [GNOME][gnome-hook]
19 | - [LibAdwaita][libadwaita-hook]
20 | - [Meson][meson-hook]
21 | - [Python][python-hook]
22 |
23 | ## Cavalier [[↑][index]]
24 |
25 | **Cavasik** is a fork of **[Cavalier][cavalier]** so we give our acknowledgments.
26 |
27 | ## Cava [[↑][index]]
28 |
29 | **Cavasik** is based on **[Cava][cava]** so we give our acknowledgments.
30 |
31 | ## Gtk [[↑][index]]
32 |
33 | **Cavasik** makes use of the **[Gtk][gtk]** so we give our acknowledgments.
34 |
35 | ## GNOME [[↑][index]]
36 |
37 | **Cavasik** uses of **[GNOME][gnome]** so we give our acknowledgments.
38 |
39 | ## LibAdwaita [[↑][index]]
40 |
41 | **Cavasik** makes use of **[LibAdwaita][libadwaita]** so we give our acknowledgments.
42 |
43 | ## Meson [[↑][index]]
44 |
45 | **Cavasik** makes use of **[Meson][meson]** so we give our acknowledgments.
46 |
47 | ## Python [[↑][index]]
48 |
49 | **Cavasik** makes use of **[Python][python]** and some libraries:
50 |
51 | - **[PyGObject][pygobject]**
52 | - **[PyCairo][pycairo]**
53 | - **[PyDBus][pydbus]**
54 |
55 | Author
56 |
61 | TheWisker
62 |
63 | [index]: https://github.com/TheWisker/Cavasik/blob/master/ACKNOWLEDGMENTS.md#acknowledgment-index
64 | [cavalier-hook]: https://github.com/TheWisker/Cavasik/blob/master/ACKNOWLEDGMENTS.md#cavalier-
65 | [cava-hook]: https://github.com/TheWisker/Cavasik/blob/master/ACKNOWLEDGMENTS.md#cava-
66 | [gtk-hook]: https://github.com/TheWisker/Cavasik/blob/master/ACKNOWLEDGMENTS.md#gtk-
67 | [gnome-hook]: https://github.com/TheWisker/Cavasik/blob/master/ACKNOWLEDGMENTS.md#gnome-
68 | [libadwaita-hook]: https://github.com/TheWisker/Cavasik/blob/master/ACKNOWLEDGMENTS.md#libadwaita-
69 | [meson-hook]: https://github.com/TheWisker/Cavasik/blob/master/ACKNOWLEDGMENTS.md#meson-
70 | [python-hook]: https://github.com/TheWisker/Cavasik/blob/master/ACKNOWLEDGMENTS.md#python-
71 |
72 | [cavalier]: https://github.com/fsobolev/cavalier
73 | [cava]: https://github.com/karlstav/cava
74 | [gtk]: https://www.gtk.org/
75 | [gnome]: https://www.gnome.org/
76 | [libadwaita]: https://gitlab.gnome.org/GNOME/libadwaita
77 | [meson]: https://mesonbuild.com/
78 | [python]: https://www.python.org/
79 | [pygobject]: https://pygobject.readthedocs.io/en/latest/
80 | [pycairo]: https://pycairo.readthedocs.io/en/latest/
81 | [pydbus]: https://pydbus.readthedocs.io/en/latest/gettingstarted.html
82 |
--------------------------------------------------------------------------------
/AUTHORS.md:
--------------------------------------------------------------------------------
1 | Cavasik
2 |
7 | Audio visualizer based on CAVA
8 |
9 | Authors
10 |
11 | - **Main**: [TheWisker](https://github.com/TheWisker) => TheWisker@protonmail.com
12 |
13 | - **Owner** of the **Cavasik** repository, has written the changes made to **Cavalier**.
14 |
15 | - **Fork Source**: [Fyodor Sobolev](https://github.com/fsobolev)
16 |
17 | - **Owner** of the **Cavalier** repository from which **Cavasik** was forked.
18 |
19 | **Other contributors** are listed in the [CONTRIBUTORS.md][contributors] file.
20 |
21 | Author
22 |
27 | TheWisker
28 |
29 | [contributors]: ./CONTRIBUTORS.md
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | Cavasik
2 |
7 | Audio visualizer based on CAVA
8 |
9 | Changelog
10 |
11 | All notable changes to this project will be documented in this file.
12 |
13 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
14 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
15 |
16 | ## [2.0.1] - 2023-08-31
17 |
18 | Minor update for **Cavasik**.
19 |
20 | ### Added
21 | - **Mirror Sync** related setting.
22 |
23 | ### Changed
24 | - No changes
25 |
26 | ### Fixed
27 | - Some typos
28 |
29 | ## [2.0.0] - 2023-06-17
30 |
31 | **First** official version of **Cavasik**.
32 |
33 | ### Added
34 |
35 | - **Circle** related settings.
36 | - **Circle** version of **Waves** and **Bars**.
37 | - All **mirror modes** and settings.
38 | - All **direction modes** and settings.
39 | - **Color animation** and it's settings.
40 | - **DBus interface** to allow changing colors externally.
41 | - **FPS** configuration option.
42 |
43 | ### Changed
44 | - Keyboard shortcuts (some removed, some added)
45 |
46 | ### Fixed
47 | - No fixes
48 |
49 | ## [1.0.0] - 2023-01-29
50 |
51 | Latest release of **Cavalier**.
52 |
53 | ### Added
54 |
55 | - New drawing mode — Particles!
56 | - Color profiles! Create as many as you want and change between them instantly. Unfortunately, this new feature required to change how the - application saves colors, and because of this your previous colors settings will be lost after installing this update.
57 | - Added keyboard shortcuts to change most of the settings on the fly.
58 | - Added option to show/hide window controls.
59 | - Added option to autohide headerbar when the main window is not focused.
60 | - Added option to change roundness of items in "levels" and "particles" modes.
61 | - Added option to reverse the order of bars.
62 | - Import/Export Settings
63 |
64 | ### Changed
65 | - No changes
66 |
67 | ### Fixed
68 | - No fixes
69 |
70 | Author
71 |
76 | TheWisker
--------------------------------------------------------------------------------
/CITATION.cff:
--------------------------------------------------------------------------------
1 | cff-version: 1.2.0
2 | message: "If you use this software in your work, or make a fork of it, please cite it using the following metadata."
3 | authors:
4 | - given-names: "TheWisker"
5 | title: "Cavasik Project"
6 | version: 2.0.1
7 | date-released: 2023-06-18
8 | url: "https://github.com/TheWisker/Cavasik"
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | Cavasik
2 |
7 | Audio visualizer based on CAVA
8 |
9 | Contributing
10 |
11 | It is great to have you here. Here are a few ways you can help make this project better!
12 |
13 | ## Team Members
14 |
15 | - Owner: [TheWisker](https://github.com/TheWisker) => TheWisker@protonmail.com
16 |
17 | ## Contributing Index
18 |
19 | - [Adding new features][new-features-hook]
20 | - [Adding a translation][translation-hook]
21 | - [Other contributions][other-contributions-hook]
22 |
23 | ## Adding new features [[↑][index]]
24 |
25 | First of all, thank you for taking the time to contribute to this project!
26 | Here are the **steps** involved when making a contribution:
27 |
28 | - Make a **fork** of this repository
29 | - **Clone** the fork locally
30 | - Make the **changes and additions** desired to the cloned fork
31 | - **Modify** the [CHANGELOG.md][changelog] file, following its structure.
32 | - **Modify** the [meson.build][meson] and [CITATION.cff][citation] file, updating their **version number** using [Semantic Versioning](https://semver.org/spec/v2.0.0.html)
33 | - Add the following **header** to newly added code files:
34 |
35 | ```
36 | # filename.ext
37 | #
38 | # Copyright (c) 2023, TheWisker
39 | # All rights reserved.
40 | #
41 | # This source code is licensed under the BSD-style license found in the
42 | # LICENSE file in the root directory of this source tree.
43 | ```
44 |
45 | - **Add** yourself or your organization to the [CONTRIBUTORS.md][contributors] file, following its structure.
46 | - Git **add**, **commit**, and **push** those changes.
47 | - Open a new pull request which will be usually reviewed in less than three days.
48 |
49 | ## Adding a translation [[↑][index]]
50 |
51 | First of all, thanks for taking the time to contribute to this project!
52 | Usually, the process of making a translation is quite **similar** to any other contribution, so follow the steps explained [here][new-features-hook].
53 | The main **difference** are the steps in the [/lang/CONTRIBUTING.md][lang-contributing] file which explains how to tamper with the translation files and **add** a new **translation**.
54 |
55 | ## Other contributions [[↑][index]]
56 |
57 | You can even contribute by adding new enhancement and improvement **ideas** to the [ideas discussion][ideas-discussion] or lending someone a hand in the repository!
58 |
59 | Author
60 |
65 | TheWisker
66 |
67 | [index]: https://github.com/TheWisker/Cavasik/blob/master/CONTRIBUTING.md#contributing-index
68 | [changelog]: ./CHANGELOG.md
69 | [meson]: ./meson.build
70 | [citation]: ./CITATION.cff
71 | [contributors]: ./CONTRIBUTORS.md
72 | [new-features-hook]: https://github.com/TheWisker/Cavasik/blob/master/CONTRIBUTING.md#adding-new-features-
73 | [translation-hook]: https://github.com/TheWisker/Cavasik/blob/master/CONTRIBUTING.md#adding-a-translation-
74 | [other-contributions-hook]: https://github.com/TheWisker/Cavasik/blob/master/CONTRIBUTING.md#other-contributions-
75 | [lang-contributing]: ./lang/CONTRIBUTING.md
76 | [ideas-discussion]: https://github.com/TheWisker/Cavasik/discussions/new?category=ideas
--------------------------------------------------------------------------------
/CONTRIBUTORS.md:
--------------------------------------------------------------------------------
1 | Cavasik
2 |
7 | Audio visualizer based on CAVA
8 |
9 | Contributors
10 |
11 | - **Main**: [TheWisker](https://github.com/TheWisker) => TheWisker@protonmail.com
12 |
13 | - **Fork Source**: [Fyodor Sobolev](https://github.com/fsobolev)
14 |
15 | These are the **Cavasik** project **contributors**, ordered from most recent to least recent:
16 |
17 | - [Myles](https://github.com/MylesGit) => Opened an [issue](https://github.com/TheWisker/Cavasik/issues/2) resulting in an enhancement.
18 | - [Kian-Meng Ang](https://github.com/kianmeng) => Made a [pull request](https://github.com/TheWisker/Cavasik/pull/5) resulting in the correction of some typos.
19 |
20 | In another format:
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | Author
29 |
34 | TheWisker
35 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Cavasik
2 |
7 | Audio visualizer based on CAVA
8 |
9 | Index
10 |
11 |
12 |
13 | [Description][description]
14 |
15 | [Features][features]
16 |
17 | [Screenshots][screenshots]
18 |
19 | [Installation][installation]
20 |
21 | [Dependencies][dependencies]
22 |
23 | [Contributions][contributions]
24 |
25 | [Translations][translations]
26 |
27 | [Metrics][metrics]
28 |
29 | [License][license]
30 |
31 | [Code of Conduct][coc]
32 |
33 | [Credits][credits]
34 |
35 |
36 |
37 | Description [↑ ]
38 |
39 | This is an audio visualizer based on CAVA with extended capabilities.
40 |
41 | Features [↑ ]
42 |
43 | The visualizer features:
44 |
45 | - Five **normal** drawing modes!
46 | - Two **circle** drawing modes!
47 | - Three **mirror** drawing modes!
48 | - Four drawing **directions**!
49 | - Customizable **LibAdwaita** interface!
50 | - Set a single color or up to a 10 color linear gradient for **background** and **foreground**!
51 | - Select different **foreground** colors for the mirrored images in **mirror** mode!
52 | - Set up a **color animation** that changes the colors gradually in a loop!
53 | - Configure *smoothing*, *noise reduction* and a few other **CAVA** settings!
54 | - Change **background** and **foreground** colors through a **DBus interface**!
55 |
56 | Screenshots [↑ ]
57 |
58 |
59 |
60 |
61 |
62 | Waves mode
63 |
64 |
65 |
66 |
67 |
68 | Levels mode
69 |
70 |
71 |
72 |
73 |
74 | Particles mode
75 |
76 |
77 |
78 |
79 |
80 | Spine mode
81 |
82 |
83 |
84 |
85 |
86 | Bars mode
87 |
88 |
89 |
90 |
91 |
92 | Waves mode + Circle shape
93 |
94 |
95 |
96 |
97 |
98 | Bars mode + Circle shape
99 |
100 |
101 |
102 |
103 |
104 | Normal mirror + Waves mode
105 |
106 |
107 |
108 |
109 |
110 | Inverted mirror + Waves mode
111 |
112 |
113 |
114 |
115 |
116 | Overlapping mirror + Waves mode
117 |
118 |
119 |
120 |
121 |
122 | Direction top-bottom + Waves mode
123 |
124 |
125 |
126 |
127 |
128 | Normal mirror + Direction left-right + Waves mode
129 |
130 |
131 |
132 |
133 |
134 | Installation [↑ ]
135 |
136 | Flathub
137 |
138 | You can install the **Cavasik** app from [Flathub][flathub] in its [app page][flathub-cavasik].
139 |
140 |
141 |
142 |
143 |
144 | - For information on how to setup *flatpak* on any distro read [this][flatpak-setup].
145 |
146 | Arch Linux
147 |
148 | You can install **Cavasik** from the [AUR][aur] repository:
149 |
150 |
151 |
152 |
153 |
154 | - For information on how to install an [AUR][aur] package read [this][aur-wiki] wiki.
155 |
156 | Manually
157 |
158 | To manually install **Cavasik** start by **downloading** a [release][releases].
159 | Then, **uncompress** the downloaded release into a resulting folder.
160 | Make sure you have all the [dependencies][dependencies] needed.
161 | Then, proceed to **run** the following commands:
162 |
163 | ```
164 | #BUILD
165 | arch-meson Cavasik build
166 | meson compile -C build
167 |
168 | #TEST
169 | meson test -C build --print-errorlog
170 |
171 | #INSTALL
172 | meson install -C build
173 | install -Dm644 Cavasik/LICENSE -t "/usr/share/licenses/cavasik"
174 | ```
175 |
176 | Dependencies [↑ ]
177 |
178 | Buildtime
179 |
180 | The **Cavasik** application has the following *buildtime* dependencies:
181 |
182 | - [meson][meson]
183 |
184 | Runtime
185 |
186 | The **Cavasik** application has the following *runtime* dependencies:
187 |
188 | - [cava][cava]
189 | - [libadwaita][libadwaita]
190 | - [python-gobject][python-gobject]
191 | - [python-cairo][python-cairo]
192 | - [python-pydbus][python-pydbus]
193 |
194 | Contributions [↑ ]
195 |
196 | First and foremost, all contributions are welcome!
197 | The **steps** involved when making a contribution are **explained** in the [CONTRIBUTING.md][contributing] file.
198 | We look forward to your contributions!
199 |
200 | - The **contributors** list is located [here][contributors].
201 |
202 | Translations [↑ ]
203 |
204 | Secondly, all translations are also welcome!
205 | The **steps** involved when making a translation are **explained** in the [CONTRIBUTING.md][contributing] file.
206 | More **specific steps** can be found in the [CONTRIBUTING.md][lang-contributing] file in the `/lang` folder.
207 | We look forward to your translations!
208 |
209 | - The **credits** of the translators are located [here][translator-credits].
210 |
211 | Metrics [↑ ]
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 | License [↑ ]
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 | Code of Conduct [↑ ]
234 |
235 | This project follows the Contributor Covenant Code of Conduct .
236 |
237 | Credits [↑ ]
238 |
239 |
240 |
241 | | Author | Forked From |
242 | | ------------- | ------------- |
243 | |
|
|
244 | | TheWisker | Fsobolev |
245 |
246 |
247 |
248 | [description]: https://github.com/TheWisker/Cavasik#description-
249 | [features]: https://github.com/TheWisker/Cavasik#features-
250 | [screenshots]: https://github.com/TheWisker/Cavasik#screenshots-
251 | [installation]: https://github.com/TheWisker/Cavasik#installation-
252 | [dependencies]: https://github.com/TheWisker/Cavasik#dependencies-
253 | [contributions]: https://github.com/TheWisker/Cavasik#contributions-
254 | [translations]: https://github.com/TheWisker/Cavasik#translations-
255 | [metrics]: https://github.com/TheWisker/Cavasik#metrics-
256 | [license]: https://github.com/TheWisker/Cavasik#license-
257 | [coc]: https://github.com/TheWisker/Cavasik#code-of-conduct-
258 | [credits]: https://github.com/TheWisker/Cavasik#credits-
259 | [flathub]: https://flathub.org/
260 | [flathub-cavasik]: https://flathub.org/apps/io.github.TheWisker.Cavasik
261 | [flatpak-setup]: https://flatpak.org/setup/
262 | [aur]: https://aur.archlinux.org/
263 | [aur-wiki]: https://wiki.archlinux.org/title/Arch_User_Repository
264 | [releases]: https://github.com/TheWisker/Cavasik/releases/
265 | [meson]: https://mesonbuild.com/
266 | [cava]: https://github.com/karlstav/cava
267 | [libadwaita]: https://gitlab.gnome.org/GNOME/libadwaita
268 | [python-gobject]: https://pygobject.readthedocs.io/en/latest/
269 | [python-cairo]: https://pycairo.readthedocs.io/en/latest/
270 | [python-pydbus]: https://pydbus.readthedocs.io/en/latest/gettingstarted.html
271 | [contributing]: ./CONTRIBUTING.md
272 | [contributors]: ./CONTRIBUTORS.md
273 | [lang-contributing]: ./lang/CONTRIBUTING.md
274 | [translator-credits]: ./lang/CREDITS.json
275 |
--------------------------------------------------------------------------------
/SUPPORT.md:
--------------------------------------------------------------------------------
1 | Cavasik
2 |
7 | Audio visualizer based on CAVA
8 |
9 | Support
10 |
11 | This page explains where and how to get help with **Cavasik**.
12 | Please **read** through the following **guidelines**.
13 |
14 | ## Support Index
15 |
16 | - [Issues][issues-hook]
17 | - [Discussions][discussions-hook]
18 | - [Contact][contact-hook]
19 |
20 | ## Issues [[↑][index]]
21 |
22 | If you are facing a **issue** or bug with the **Cavasik** application, you can **report it** through the [issues page][issues].
23 | In said page you can report from **bugs** to possible **enhancements**, so feel free to do so!
24 |
25 | ## Discussions [[↑][index]]
26 |
27 | If you want to share a **new idea** for the **Cavasik** project, have some **questions** or just something more **general** to discuss, you can do so in the [discussions page][discussions].
28 |
29 | ## Contact [[↑][index]]
30 |
31 | If you have been treated in a way that violates our [Code of Conduct][coc] you can contact me with this email:
32 |
33 | - TheWisker@protonmail.com
34 |
35 | Author
36 |
41 | TheWisker
42 |
43 | [index]: https://github.com/TheWisker/Cavasik/blob/master/SUPPORT.md#support-index
44 | [issues-hook]: https://github.com/TheWisker/Cavasik/blob/master/SUPPORT.md#issues-
45 | [discussions-hook]: https://github.com/TheWisker/Cavasik/blob/master/SUPPORT.md#discussions-
46 | [contact-hook]: https://github.com/TheWisker/Cavasik/blob/master/SUPPORT.md#contact-
47 | [issues]: https://github.com/TheWisker/Cavasik/issues
48 | [discussions]: https://github.com/TheWisker/Cavasik/discussions
49 | [coc]: ./.github/CODE_OF_CONDUCT.md
--------------------------------------------------------------------------------
/assets/css/cavasik.css:
--------------------------------------------------------------------------------
1 | /*
2 | cavasik.css
3 |
4 | Copyright (c) 2023, TheWisker
5 | All rights reserved.
6 |
7 | This source code is licensed under the BSD-style license found in the
8 | LICENSE file in the root directory of this source tree.
9 | */
10 |
11 | .sharp-corners {
12 | border-top-left-radius: 0px;
13 | border-top-right-radius: 0px;
14 | border-bottom-left-radius: 0px;
15 | border-bottom-right-radius: 0px;
16 | }
17 |
18 | .borderless-window {
19 | outline-width: 0px;
20 | box-shadow: none;
21 | }
22 |
--------------------------------------------------------------------------------
/assets/fork_profile.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TheWisker/Cavasik/65e8eaa46383070b5de165485dc9af984a00ec62/assets/fork_profile.png
--------------------------------------------------------------------------------
/assets/icons/io.github.TheWisker.Cavasik-symbolic.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/assets/icons/io.github.TheWisker.Cavasik.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TheWisker/Cavasik/65e8eaa46383070b5de165485dc9af984a00ec62/assets/icons/io.github.TheWisker.Cavasik.png
--------------------------------------------------------------------------------
/assets/icons/io.github.TheWisker.Cavasik.svg:
--------------------------------------------------------------------------------
1 |
2 |
15 |
17 |
20 |
24 |
28 |
32 |
33 |
36 |
40 |
44 |
48 |
49 |
59 |
69 |
70 |
89 |
97 |
101 |
105 |
109 |
113 |
117 |
121 |
122 |
125 |
130 |
139 |
143 |
147 |
148 |
149 |
--------------------------------------------------------------------------------
/assets/icons/meson.build:
--------------------------------------------------------------------------------
1 | install_data(
2 | ('@0@.svg').format('io.github.TheWisker.Cavasik'),
3 | install_dir: join_paths(get_option('datadir'), 'icons', 'hicolor', 'scalable', 'apps')
4 | )
5 |
6 | install_data(
7 | ('@0@-symbolic.svg').format('io.github.TheWisker.Cavasik'),
8 | install_dir: join_paths(get_option('datadir'), 'icons', 'hicolor', 'symbolic', 'apps')
9 | )
10 |
--------------------------------------------------------------------------------
/assets/io.github.TheWisker.Cavasik.desktop.in:
--------------------------------------------------------------------------------
1 | [Desktop Entry]
2 | Name=Cavasik
3 | Comment=Audio Visualizer
4 | Exec=cavasik
5 | Icon=io.github.TheWisker.Cavasik
6 | Terminal=false
7 | Type=Application
8 | Categories=AudioVideo;Audio;GTK;Amusement;
9 | StartupNotify=true
10 |
--------------------------------------------------------------------------------
/assets/io.github.TheWisker.Cavasik.gschema.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Window size
6 | (400,200)
7 |
8 |
9 | Window maximized state
10 | Whether main window is maximized or not
11 | false
12 |
13 |
14 | Borderless window
15 | Whether to disable window shadow and borders.
16 | false
17 |
18 |
19 | Window controls
20 | Whether to show window control buttons.
21 | false
22 |
23 |
24 | Autohide headerbar
25 | Whether to hide headerbar when main window is not focused.
26 | false
27 |
28 |
29 | DBus colors
30 | Whether to use colors received through DBus (overwrites default profile).
31 | false
32 |
33 |
34 | DBus opacity
35 | Opacity for the colors received through DBus.
36 |
37 | 100
38 |
39 |
40 | Drawing mode
41 | Defines what the visualizer looks like.
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 | "wave"
50 |
51 |
52 | Drawing direction
53 |
54 |
55 |
56 |
57 |
58 |
59 | "bottom-top"
60 |
61 |
62 | Mirror mode
63 |
64 |
65 |
66 |
67 |
68 |
69 | "none"
70 |
71 |
72 | Mirror offset
73 | Offset between mirrored images in normal mode
74 |
75 | 0
76 |
77 |
78 | Mirror opacity
79 | Opacity of the mirrored image in normal mode
80 |
81 | 100
82 |
83 |
84 | Mirror colors
85 | Color profile for mirrored images
86 | 0
87 |
88 |
89 | Sync mirror
90 | Sync mirror color profile with normal color profile
91 | true
92 |
93 |
94 | Mirror clones
95 | Clones to make in the overlapping mode
96 |
97 | 1
98 |
99 |
100 | Mirror ratio
101 | Scale ratio between overlapping clones
102 |
103 | 0.8
104 |
105 |
106 | Circle
107 | Whether to modify drawing modes to draw in circle
108 | false
109 |
110 |
111 | Wave - Show Inner Circle
112 | Whether to show the inner circle when using Wave mode Circle variant.
113 | false
114 |
115 |
116 | Radius
117 | Radius of base circle (in percent)
118 |
119 | 40
120 |
121 |
122 | Drawing area margin
123 | Size of gaps around drawing area (in pixels).
124 |
125 | 0
126 |
127 |
128 | Offset between items
129 | The size of spaces between elements in "levels", "particles" and "bars" modes (in percent).
130 |
131 | 10
132 |
133 |
134 | Roundness of items
135 | This setting affects "levels", "particles" and "spine" modes.
136 |
137 | 10
138 |
139 |
140 | Thickness of lines
141 | Thickness of lines when filling is off (in pixels).
142 |
143 | 15
144 |
145 |
146 | Filling
147 | Whether shapes should be filled or outlined.
148 | true
149 |
150 |
151 | Number of bars
152 | Number of bars in CAVA config
153 |
154 | 12
155 |
156 |
157 | Automatic sensitivity
158 | Attempt to decrease sensitivity if the bars peak.
159 | true
160 |
161 |
162 | Sensitivity
163 | Manual sensitivity. If automatic sensitivity is enabled, this will only be the initial value.
164 |
165 | 10.0
166 |
167 |
168 | Channels
169 | Mono or stereo
170 |
171 |
172 |
173 |
174 | "stereo"
175 |
176 |
177 | Smoothing
178 |
179 |
180 |
181 |
182 | "monstercat"
183 |
184 |
185 | Noise reduction
186 | This factor adjusts the integral and gravity filters to keep the signal smooth. 1 will be very slow and smooth, 0 will be fast but noisy.
187 |
188 | 0.77
189 |
190 |
191 | Reverse order
192 | true
193 |
194 |
195 | Frames per Second
196 |
197 | 60
198 |
199 |
200 | Widgets style
201 | Style used by Adwaita widgets.
202 |
203 |
204 |
205 |
206 | "dark"
207 |
208 |
209 | Sharp corners
210 | Whether the main window corners should be sharp.
211 | false
212 |
213 |
214 | Color Profiles
215 | []
216 |
217 |
218 | Index of active color profile
219 | 0
220 |
221 |
222 | Color animation
223 | Whether the color animation is active
224 | false
225 |
226 |
227 | Color animation target
228 | The target color profile to fade in and out of
229 | 0
230 |
231 |
232 | Color animation mirror target
233 | The target color profile to fade in and out of for the mirrored image
234 | 0
235 |
236 |
237 | Color animation length
238 | The length in seconds of the color animation
239 |
240 | 5
241 |
242 |
243 |
244 |
--------------------------------------------------------------------------------
/assets/io.github.TheWisker.Cavasik.metainfo.xml.in:
--------------------------------------------------------------------------------
1 |
2 |
3 | io.github.TheWisker.Cavasik
4 | CC0-1.0
5 | GPL-3.0
6 | Cavasik
7 | Audio visualizer based on CAVA
8 |
9 | Cavasik is an audio visualizer based on CAVA
10 | Cavasik is a fork of Cavalier.
11 |
12 | Five normal drawing modes!
13 | Two circle drawing modes!
14 | Three mirror drawing modes!
15 | Four drawing directions!
16 | Customizable LibAdwaita interface!
17 | Set a single color or up to a 10 color linear gradient for background and foreground!
18 | Select different foreground colors for the mirrored images in mirror mode!
19 | Set up a color animation that changes the colors gradually in a loop!
20 | Configure smoothing, noise reduction and a few other CAVA settings!
21 | Change background and foreground colors through a DBus interface!
22 |
23 |
24 | io.github.TheWisker.Cavasik.desktop
25 | https://github.com/TheWisker/Cavasik
26 | https://github.com/TheWisker/Cavasik/issues
27 | TheWisker
28 | Cavasik
29 |
30 |
31 | https://raw.githubusercontent.com/TheWisker/Cavasik/master/assets/screenshots/main.png
32 |
33 |
34 | https://raw.githubusercontent.com/TheWisker/Cavasik/master/assets/screenshots/waves.png
35 |
36 |
37 | https://raw.githubusercontent.com/TheWisker/Cavasik/master/assets/screenshots/levels.png
38 |
39 |
40 | https://raw.githubusercontent.com/TheWisker/Cavasik/master/assets/screenshots/particles.png
41 |
42 |
43 | https://raw.githubusercontent.com/TheWisker/Cavasik/master/assets/screenshots/spine.png
44 |
45 |
46 | https://raw.githubusercontent.com/TheWisker/Cavasik/master/assets/screenshots/bars.png
47 |
48 |
49 | https://raw.githubusercontent.com/TheWisker/Cavasik/master/assets/screenshots/wave_circle.png
50 |
51 |
52 | https://raw.githubusercontent.com/TheWisker/Cavasik/master/assets/screenshots/bars_circle.png
53 |
54 |
55 | https://raw.githubusercontent.com/TheWisker/Cavasik/master/assets/screenshots/mirror_normal.png
56 |
57 |
58 | https://raw.githubusercontent.com/TheWisker/Cavasik/master/assets/screenshots/mirror_inverted.png
59 |
60 |
61 | https://raw.githubusercontent.com/TheWisker/Cavasik/master/assets/screenshots/mirror_overlapping.png
62 |
63 |
64 | https://raw.githubusercontent.com/TheWisker/Cavasik/master/assets/screenshots/direction_top.png
65 |
66 |
67 |
68 |
69 |
70 | Second release of Cavasik
71 | Cavasik is an audio visualizer based on CAVA with customizable LibAdwaita interface.
72 | Cavasik is a fork of Cavalier.
73 |
74 | Some minor changes!
75 |
76 |
77 |
78 |
79 |
80 | First release of Cavasik
81 | Cavasik is an audio visualizer based on CAVA with customizable LibAdwaita interface.
82 | Cavasik is a fork of Cavalier.
83 |
84 | Five normal drawing modes!
85 | Two circle drawing modes!
86 | Three mirror drawing modes!
87 | Four drawing directions!
88 | Set a single color or up to a 10 color linear gradient for background and foreground!
89 | Select different foreground colors for the mirrored images in mirror mode!
90 | Set up a color animation that changes the colors gradually in a loop!
91 | Configure smoothing, noise reduction and a few other CAVA settings!
92 | Change background and foreground colors through a DBus interface!
93 |
94 |
95 |
96 |
97 |
98 |
99 | pointing
100 | keyboard
101 | touch
102 |
103 |
104 | 360
105 |
106 |
107 | workstation
108 | mobile
109 |
110 |
111 |
--------------------------------------------------------------------------------
/assets/meson.build:
--------------------------------------------------------------------------------
1 | desktop_file = i18n.merge_file(
2 | input: 'io.github.TheWisker.Cavasik.desktop.in',
3 | output: 'io.github.TheWisker.Cavasik.desktop',
4 | type: 'desktop',
5 | po_dir: '../lang',
6 | install: true,
7 | install_dir: join_paths(get_option('datadir'), 'applications')
8 | )
9 |
10 | desktop_utils = find_program('desktop-file-validate', required: false)
11 | if desktop_utils.found()
12 | test('Validate desktop file', desktop_utils, args: [desktop_file])
13 | endif
14 |
15 | appstream_file = i18n.merge_file(
16 | input: 'io.github.TheWisker.Cavasik.metainfo.xml.in',
17 | output: 'io.github.TheWisker.Cavasik.metainfo.xml',
18 | po_dir: '../lang',
19 | install: true,
20 | install_dir: join_paths(get_option('datadir'), 'metainfo')
21 | )
22 |
23 | appstream_util = find_program('appstream-util', required: false)
24 | if appstream_util.found()
25 | test('Validate appstream file', appstream_util, args: ['validate-relax', appstream_file])
26 | endif
27 |
28 | install_data('io.github.TheWisker.Cavasik.gschema.xml', install_dir: join_paths(get_option('datadir'), 'glib-2.0/schemas'))
29 |
30 | gschema_util = find_program('glib-compile-schemas', required: false)
31 | if gschema_util.found()
32 | test('Validate gschema file', gschema_util, args: ['--strict', '--dry-run', meson.current_source_dir()])
33 | endif
34 |
35 | subdir('icons')
--------------------------------------------------------------------------------
/assets/metrics/base.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | TheWisker/Cavasik
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | Created 1 year ago
23 |
24 |
25 |
26 |
27 |
28 | Deployed 728 times
29 |
30 |
31 |
32 |
33 |
34 | 7.91 MB used
35 |
36 |
37 |
38 |
39 |
40 | 131 views in last two weeks
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 | 2 Environments
69 |
70 |
71 |
72 |
73 |
74 | 0 added, 0 removed
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
--------------------------------------------------------------------------------
/assets/metrics/languages.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | 4 Languages
15 |
16 |
17 |
18 | Most used languages
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 | Python
37 |
38 |
39 | 1.28k lines
40 | 54.6 kB
41 | 88.88%
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 | Shell
50 |
51 |
52 | 111 lines
53 | 2.33 kB
54 | 3.8%
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 | Meson
65 |
66 |
67 | 87 lines
68 | 4.13 kB
69 | 6.71%
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 | CSS
78 |
79 |
80 | 17 lines
81 | 382 bytes
82 | 0.62%
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
--------------------------------------------------------------------------------
/assets/metrics/licenses.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | Licenses
15 |
16 |
17 |
18 |
19 |
20 |
21 | Unexpected error
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/assets/profile.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TheWisker/Cavasik/65e8eaa46383070b5de165485dc9af984a00ec62/assets/profile.png
--------------------------------------------------------------------------------
/assets/screenshots/bars.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TheWisker/Cavasik/65e8eaa46383070b5de165485dc9af984a00ec62/assets/screenshots/bars.png
--------------------------------------------------------------------------------
/assets/screenshots/bars_circle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TheWisker/Cavasik/65e8eaa46383070b5de165485dc9af984a00ec62/assets/screenshots/bars_circle.png
--------------------------------------------------------------------------------
/assets/screenshots/direction_top.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TheWisker/Cavasik/65e8eaa46383070b5de165485dc9af984a00ec62/assets/screenshots/direction_top.png
--------------------------------------------------------------------------------
/assets/screenshots/levels.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TheWisker/Cavasik/65e8eaa46383070b5de165485dc9af984a00ec62/assets/screenshots/levels.png
--------------------------------------------------------------------------------
/assets/screenshots/main.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TheWisker/Cavasik/65e8eaa46383070b5de165485dc9af984a00ec62/assets/screenshots/main.png
--------------------------------------------------------------------------------
/assets/screenshots/mirror_column.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TheWisker/Cavasik/65e8eaa46383070b5de165485dc9af984a00ec62/assets/screenshots/mirror_column.png
--------------------------------------------------------------------------------
/assets/screenshots/mirror_inverted.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TheWisker/Cavasik/65e8eaa46383070b5de165485dc9af984a00ec62/assets/screenshots/mirror_inverted.png
--------------------------------------------------------------------------------
/assets/screenshots/mirror_normal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TheWisker/Cavasik/65e8eaa46383070b5de165485dc9af984a00ec62/assets/screenshots/mirror_normal.png
--------------------------------------------------------------------------------
/assets/screenshots/mirror_overlapping.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TheWisker/Cavasik/65e8eaa46383070b5de165485dc9af984a00ec62/assets/screenshots/mirror_overlapping.png
--------------------------------------------------------------------------------
/assets/screenshots/particles.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TheWisker/Cavasik/65e8eaa46383070b5de165485dc9af984a00ec62/assets/screenshots/particles.png
--------------------------------------------------------------------------------
/assets/screenshots/spine.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TheWisker/Cavasik/65e8eaa46383070b5de165485dc9af984a00ec62/assets/screenshots/spine.png
--------------------------------------------------------------------------------
/assets/screenshots/wave_circle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TheWisker/Cavasik/65e8eaa46383070b5de165485dc9af984a00ec62/assets/screenshots/wave_circle.png
--------------------------------------------------------------------------------
/assets/screenshots/waves.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TheWisker/Cavasik/65e8eaa46383070b5de165485dc9af984a00ec62/assets/screenshots/waves.png
--------------------------------------------------------------------------------
/aur/cavasik-git/.SRCINFO:
--------------------------------------------------------------------------------
1 | pkgbase = cavasik-git
2 | pkgdesc = Audio visualizer based on CAVA
3 | pkgver = v2.0.1
4 | pkgrel = 2
5 | url = https://github.com/TheWisker/Cavasik
6 | arch = any
7 | license = GPL3
8 | checkdepends = appstream-glib
9 | makedepends = git
10 | makedepends = meson
11 | depends = cava
12 | depends = libadwaita
13 | depends = python-cairo
14 | depends = python-gobject
15 | depends = python-pydbus
16 | conflicts = cavasik
17 | source = git+https://github.com/TheWisker/Cavasik.git
18 | b2sums = SKIP
19 |
20 | pkgname = cavasik-git
21 |
--------------------------------------------------------------------------------
/aur/cavasik-git/PKGBUILD:
--------------------------------------------------------------------------------
1 | # Maintainer: TheWisker
2 |
3 | pkgname=cavasik-git
4 | pkgbase=cavasik-git
5 | pkgver=v2.0.1
6 | pkgrel=2
7 | pkgdesc='Audio visualizer based on CAVA'
8 | arch=(any)
9 | url=https://github.com/TheWisker/Cavasik
10 | license=('GPL3')
11 | depends=(cava libadwaita python-cairo python-gobject python-pydbus)
12 | makedepends=(git meson)
13 | checkdepends=(appstream-glib)
14 | optdepends=()
15 | provides=()
16 | conflicts=("${pkgname%-git}")
17 | replaces=()
18 | backup=()
19 | options=()
20 | install=
21 | changelog=
22 | source=("git+$url.git")
23 | b2sums=('SKIP')
24 |
25 | pkgver() {
26 | echo "$pkgver.r$pkgrel"
27 | }
28 |
29 | build() {
30 | arch-meson Cavasik build
31 | meson compile -C build
32 | }
33 |
34 | check() {
35 | meson test -C build --print-errorlog
36 | }
37 |
38 | package() {
39 | DESTDIR="$pkgdir" meson install -C build
40 | install -Dm644 Cavasik/LICENSE -t "$pkgdir/usr/share/licenses/${pkgname%-git}"
41 | }
42 |
--------------------------------------------------------------------------------
/aur/cavasik/.SRCINFO:
--------------------------------------------------------------------------------
1 | pkgbase = cavasik
2 | pkgdesc = Audio visualizer based on CAVA
3 | pkgver = v2.0.1
4 | pkgrel = 1
5 | url = https://github.com/TheWisker/Cavasik
6 | arch = any
7 | license = GPL3
8 | checkdepends = appstream-glib
9 | makedepends = git
10 | makedepends = meson
11 | depends = cava
12 | depends = libadwaita
13 | depends = python-gobject
14 | depends = python-cairo
15 | depends = python-pydbus
16 | conflicts = cavasik-git
17 | source = git+https://github.com/TheWisker/Cavasik#tag=v2.0.1
18 | b2sums = SKIP
19 |
20 | pkgname = cavasik
21 |
--------------------------------------------------------------------------------
/aur/cavasik/PKGBUILD:
--------------------------------------------------------------------------------
1 | # Maintainer: TheWisker
2 |
3 | pkgname=cavasik
4 | pkgbase=cavasik
5 | pkgver=v2.0.1
6 | pkgrel=1
7 | pkgdesc='Audio visualizer based on CAVA'
8 | arch=(any)
9 | url=https://github.com/TheWisker/${pkgname^}
10 | license=('GPL3')
11 | depends=(cava libadwaita python-gobject python-cairo python-pydbus)
12 | makedepends=(git meson)
13 | checkdepends=(appstream-glib)
14 | optdepends=()
15 | provides=()
16 | conflicts=("${pkgname}-git")
17 | replaces=()
18 | backup=()
19 | options=()
20 | install=
21 | changelog=
22 | source=("git+$url#tag=$pkgver")
23 | b2sums=('SKIP')
24 |
25 | build() {
26 | arch-meson ${pkgname^} build
27 | meson compile -C build
28 | }
29 |
30 | check() {
31 | meson test -C build --print-errorlog
32 | }
33 |
34 | package() {
35 | DESTDIR="$pkgdir" meson install -C build
36 | install -Dm644 ${pkgname^}/LICENSE -t "$pkgdir/usr/share/licenses/$pkgname"
37 | }
38 |
--------------------------------------------------------------------------------
/cavasik.doap:
--------------------------------------------------------------------------------
1 |
6 |
7 | Cavasik
8 | Audio visualizer based on CAVA
9 |
10 |
11 |
12 |
13 | Python
14 |
15 |
16 |
17 | TheWisker
18 | thewisker
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/io.github.TheWisker.Cavasik.json:
--------------------------------------------------------------------------------
1 | {
2 | "app-id" : "io.github.TheWisker.Cavasik",
3 | "runtime" : "org.gnome.Platform",
4 | "runtime-version" : "44",
5 | "sdk" : "org.gnome.Sdk",
6 | "command" : "cavasik",
7 | "finish-args" : [
8 | "--share=ipc",
9 | "--socket=wayland",
10 | "--socket=fallback-x11",
11 | "--socket=pulseaudio",
12 | "--device=dri"
13 | ],
14 | "cleanup" : [
15 | "/include",
16 | "/lib/pkgconfig",
17 | "/man",
18 | "/share/doc",
19 | "/share/gtk-doc",
20 | "/share/man",
21 | "/share/pkgconfig",
22 | "*.la",
23 | "*.a"
24 | ],
25 | "modules" : [
26 | "shared-modules/linux-audio/fftw3f.json",
27 | {
28 | "name" : "iniparser",
29 | "buildsystem" : "simple",
30 | "build-commands" :
31 | [
32 | "make PREFIX=/app",
33 | "install -Dm0644 src/iniparser.h /app/include/iniparser.h",
34 | "install -Dm0644 src/dictionary.h /app/include/dictionary.h",
35 | "install -Dm0644 libiniparser.so.1 /app/lib/libiniparser.so.1",
36 | "ln -sf libiniparser.so.1 /app/lib/libiniparser.so"
37 | ],
38 | "sources" : [
39 | {
40 | "type" : "git",
41 | "url" : "https://github.com/ndevilla/iniparser.git",
42 | "commit" : "deb85ad4936d4ca32cc2260ce43323d47936410d"
43 | }
44 | ]
45 | },
46 | {
47 | "name" : "cava",
48 | "sources" : [
49 | {
50 | "type" : "git",
51 | "url" : "https://github.com/karlstav/cava.git",
52 | "tag" : "0.8.3",
53 | "commit" : "746a3b1e6021e383aea9d0000f49d71fb24e1856"
54 | }
55 | ]
56 | },
57 | {
58 | "name": "python3-pydbus",
59 | "buildsystem": "simple",
60 | "build-commands": [
61 | "pip3 install --verbose --exists-action=i --no-index --find-links=\"file://${PWD}\" --prefix=${FLATPAK_DEST} \"pydbus\" --no-build-isolation"
62 | ],
63 | "sources": [
64 | {
65 | "type": "file",
66 | "url": "https://files.pythonhosted.org/packages/92/56/27148014c2f85ce70332f18612f921f682395c7d4e91ec103783be4fce00/pydbus-0.6.0-py2.py3-none-any.whl",
67 | "sha256": "66b80106352a718d80d6c681dc2a82588048e30b75aab933e4020eb0660bf85e"
68 | }
69 | ]
70 | },
71 | {
72 | "name" : "cavasik",
73 | "builddir" : true,
74 | "buildsystem" : "meson",
75 | "sources" : [
76 | {
77 | "type" : "dir",
78 | "path" : "."
79 | }
80 | ]
81 | }
82 | ]
83 | }
84 |
--------------------------------------------------------------------------------
/lang/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | Cavasik
2 |
7 | Audio visualizer based on CAVA
8 |
9 | New Translations
10 |
11 | If you want to make a translation, thank you, you're awesome!
12 |
13 | Here are the instructions to make a translation:
14 |
15 | - Make a **fork** of the repository.
16 | - **Clone** said fork.
17 | - (**Optional**) Use a **specialized editor** like [poedit](https://poedit.net/).
18 | - Create a **`.po`** file in the `lang` directory.
19 | - **Copy** the contents of `cavasik.pot` **template** to said file.
20 | - Work with the new `.po` file using a normal or a specialized editor.
21 | - When the translation is **finished**, add the **locale code** to the `LINGUAS` file keeping it **alphabetically** sorted.
22 | - Then, to take **authorship**, **edit** the `CREDITS.json` file adding a **new entry** following the same pattern as the other ones. If you made multiple translations use an array of languages in the "lang" entry.
23 | - **Add**, **commit** and **push** the changes to the forked repository.
24 | - Lastly, open a new pull request with the new changes made!
25 |
26 | Thank you for being willing to sacrifice your time to help this project grow!
27 |
28 | Author
29 |
34 | TheWisker
--------------------------------------------------------------------------------
/lang/CREDITS.json:
--------------------------------------------------------------------------------
1 | {
2 | "Fyodor Sobolev": {
3 | "lang": "Russian",
4 | "url": "https://github.com/fsobolev"
5 | },
6 | "Heimen Stoffels": {
7 | "lang": "Dutch",
8 | "url": "https://github.com/Vistaus"
9 | },
10 | "Albano Battistella": {
11 | "lang": "Italian",
12 | "url": "https://github.com/albanobattistella"
13 | },
14 | "gregorni": {
15 | "lang": "German",
16 | "url": "https://gitlab.com/gregorni"
17 | },
18 | "Irénée Thirion": {
19 | "lang": "French",
20 | "url": "https://github.com/rene-coty"
21 | },
22 | "Santiago F.": {
23 | "lang": "Spanish",
24 | "email": "sf@santyf.com"
25 | },
26 | "TheWisker": {
27 | "lang": "Japanese",
28 | "url": "https://github.com/TheWisker"
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/lang/LINGUAS:
--------------------------------------------------------------------------------
1 | # List of avaiable translations (sorted alphabetically)
2 | de
3 | es
4 | fr
5 | it
6 | jp
7 | nl
8 | pt_BR
9 | ru
10 |
--------------------------------------------------------------------------------
/lang/POTFILES.in:
--------------------------------------------------------------------------------
1 | # POTFILES.in
2 | #
3 | # Copyright (c) 2023, TheWisker
4 | # All rights reserved.
5 | #
6 | # This source code is licensed under the BSD-style license found in the
7 | # LICENSE file in the root directory of this source tree.
8 | #
9 |
10 | # Data files
11 | assets/io.github.TheWisker.Cavasik.desktop.in
12 | assets/io.github.TheWisker.Cavasik.metainfo.xml.in
13 | assets/io.github.TheWisker.Cavasik.gschema.xml
14 |
15 | # Source files
16 | src/main.py
17 | src/window.py
18 | src/preferences_window.py
19 | src/shortcuts_dialog.ui
20 |
--------------------------------------------------------------------------------
/lang/meson.build:
--------------------------------------------------------------------------------
1 | i18n.gettext('cavasik', preset: 'glib')
2 |
3 | install_data('CREDITS.json', install_dir: pkgdatadir)
--------------------------------------------------------------------------------
/meson.build:
--------------------------------------------------------------------------------
1 | project('cavasik', version: 'v2.0.1', meson_version: '>= 0.59.0', default_options: ['warning_level=2', 'werror=false'])
2 |
3 | i18n = import('i18n')
4 | gnome = import('gnome')
5 |
6 | pkgdatadir = join_paths(get_option('prefix'), get_option('datadir'), meson.project_name())
7 |
8 | subdir('assets')
9 | subdir('src')
10 | subdir('lang')
11 |
12 | gnome.post_install(glib_compile_schemas: true, gtk_update_icon_cache: true, update_desktop_database: true)
--------------------------------------------------------------------------------
/src/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TheWisker/Cavasik/65e8eaa46383070b5de165485dc9af984a00ec62/src/__init__.py
--------------------------------------------------------------------------------
/src/cava.py:
--------------------------------------------------------------------------------
1 | # cava.py
2 | #
3 | # Copyright (c) 2023, TheWisker
4 | # All rights reserved.
5 | #
6 | # This source code is licensed under the BSD-style license found in the
7 | # LICENSE file in the root directory of this source tree.
8 |
9 | import os
10 | import subprocess
11 | import struct
12 | from cavasik.settings import CavasikSettings
13 |
14 | class Cava:
15 | def __init__(self):
16 | self.BYTETYPE = "H"
17 | self.BYTESIZE = 2
18 | self.BYTENORM = 65535
19 | self.restarting = False
20 |
21 | self.settings = CavasikSettings.new()
22 | self.fps = self.settings['fps']
23 |
24 | self.sample = []
25 |
26 | if os.getenv('XDG_CONFIG_HOME'):
27 | self.config_dir = os.getenv('XDG_CONFIG_HOME') + '/cavasik'
28 | else:
29 | self.config_dir = os.getenv('HOME') + '/.config/cavasik'
30 | if not os.path.isdir(self.config_dir):
31 | os.makedirs(self.config_dir)
32 | self.config_file_path = self.config_dir + '/config'
33 |
34 | def run(self):
35 | self.load_settings()
36 | self.write_config()
37 | self.process = subprocess.Popen(["cava", "-p", self.config_file_path], \
38 | stdout=subprocess.PIPE)
39 | source = self.process.stdout
40 | self.restarting = False
41 | self.chunk = self.BYTESIZE * self.bars
42 | self.fmt = self.BYTETYPE * self.bars
43 | while True:
44 | data = source.read(self.chunk)
45 | if len(data) < self.chunk or self.restarting:
46 | break
47 | self.sample = \
48 | [i / self.BYTENORM for i in struct.unpack(self.fmt, data)]
49 |
50 | def stop(self):
51 | if not self.restarting:
52 | self.process.kill()
53 |
54 | def load_settings(self):
55 | # Cava config options
56 | self.bars = self.settings['bars']
57 | self.autosens = int(self.settings['autosens'])
58 | self.sensitivity = self.settings['sensitivity']
59 | self.channels = self.settings['channels']
60 | self.monstercat = \
61 | ['off', 'monstercat'].index(self.settings['smoothing'])
62 | self.noise_reduction = self.settings['noise-reduction']
63 | print(self.noise_reduction)
64 |
65 | def write_config(self):
66 | try:
67 | f = open(self.config_file_path, 'w')
68 | conf = '\n'.join([
69 | '[general]',
70 | f'bars = {self.bars}',
71 | f'autosens = {self.autosens}',
72 | f'sensitivity = {self.sensitivity ** 2}',
73 | f'framerate = {self.fps}',
74 | '[input]',
75 | 'method = pulse',
76 | '[output]',
77 | f'channels = {self.channels}',
78 | 'mono_option = average',
79 | 'method = raw',
80 | 'raw_target = /dev/stdout',
81 | 'bit_format = 16bit',
82 | '[smoothing]',
83 | f'monstercat = {self.monstercat}',
84 | f'noise_reduction = {self.noise_reduction}'
85 | ])
86 | print(conf)
87 | f.write(conf)
88 | f.close()
89 | except Exception as e:
90 | print("Can't write config file for cava...'")
91 | print(e)
92 |
93 |
--------------------------------------------------------------------------------
/src/cavasik.gresource.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | ../assets/css/cavasik.css
5 | shortcuts_dialog.ui
6 |
7 |
8 |
--------------------------------------------------------------------------------
/src/cavasik.in:
--------------------------------------------------------------------------------
1 | #!@PYTHON@
2 |
3 | # cavasik.in
4 | #
5 | # Copyright (c) 2023, TheWisker
6 | # All rights reserved.
7 | #
8 | # This source code is licensed under the BSD-style license found in the
9 | # LICENSE file in the root directory of this source tree.
10 |
11 | import os
12 | import sys
13 | import signal
14 | import locale
15 | import gettext
16 |
17 | VERSION = '@VERSION@'
18 | pkgdatadir = '@pkgdatadir@'
19 | localedir = '@localedir@'
20 |
21 | sys.path.insert(1, pkgdatadir)
22 | signal.signal(signal.SIGINT, signal.SIG_DFL)
23 | locale.bindtextdomain('cavasik', localedir)
24 | locale.textdomain('cavasik')
25 | gettext.install('cavasik', localedir)
26 |
27 | if __name__ == '__main__':
28 | import gi
29 |
30 | from gi.repository import Gio
31 | resource = Gio.Resource.load(os.path.join(pkgdatadir, 'cavasik.gresource'))
32 | resource._register()
33 |
34 | from cavasik import main
35 | sys.exit(main.main(VERSION))
36 |
--------------------------------------------------------------------------------
/src/dbus.py:
--------------------------------------------------------------------------------
1 | # dbus.py
2 | #
3 | # Copyright (c) 2023, TheWisker
4 | # All rights reserved.
5 | #
6 | # This source code is licensed under the BSD-style license found in the
7 | # LICENSE file in the root directory of this source tree.
8 |
9 | from pydbus import SessionBus
10 | from gi.repository import Adw
11 | from cavasik.settings import CavasikSettings
12 |
13 | class BusInterface(object):
14 | """
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | """
28 |
29 | def __init__(self):
30 | self.window = None
31 | self.bus = SessionBus()
32 | self.bus.publish('io.github.TheWisker.Cavasik', self)
33 | self.settings = CavasikSettings.new(self.on_settings_changed)
34 | self.active = self.settings['dbus-colors']
35 |
36 | def set_fg_colors(self, path):
37 | if self.active:
38 | return self._change_colors(path, True) # True for foreground colors
39 | return False
40 |
41 | def set_bg_colors(self, path):
42 | if self.active:
43 | return self._change_colors(path, False) # False for background colors
44 | return False
45 |
46 | def _change_colors(self, path, cid):
47 | try:
48 | colors = []
49 | with open(path, "r") as file:
50 | for line in file:
51 | line = line.strip() # Remove leading/trailing whitespaces
52 | if line: # Skip empty lines
53 | color = [float(c) for c in line.split(",")]
54 | color += [self.settings['dbus-opacity']/100] if len(color) < 4 else []
55 | colors.append(tuple(color))
56 | profiles = self.settings['color-profiles']
57 | profiles[0] = (profiles[0][0], colors if cid else profiles[0][1], profiles[0][2] if cid else colors)
58 | self.settings['active-color-profile'] = 0
59 | self.settings['color-profiles'] = profiles
60 | return True
61 | except FileNotFoundError:
62 | print(f"File not found: {path}")
63 | except IOError as e:
64 | print(f"An error occurred while reading the file: {e}")
65 | except Exception as e:
66 | print(f"An unexpected error occurred: {e}")
67 | return False
68 |
69 | def on_settings_changed(self):
70 | self.active = self.settings['dbus-colors']
71 |
72 |
--------------------------------------------------------------------------------
/src/draw_functions.py:
--------------------------------------------------------------------------------
1 | # draw_functions.py
2 | #
3 | # Copyright (c) 2023, TheWisker
4 | # All rights reserved.
5 | #
6 | # This source code is licensed under the BSD-style license found in the
7 | # LICENSE file in the root directory of this source tree.
8 |
9 | import cairo
10 | import math
11 |
12 | def set_source(cr, height, colors, offset=0):
13 | if len(colors) > 1:
14 | pat = cairo.LinearGradient(0.0, height * offset, 0.0, \
15 | height + height * offset)
16 | for i in range(len(colors)):
17 | (red, green, blue, alpha) = colors[i]
18 | pat.add_color_stop_rgba(1 / (len(colors) - 1) * i, \
19 | red / 255, green / 255, blue / 255, alpha)
20 | cr.set_source(pat)
21 | else:
22 | (red, green, blue, alpha) = colors[0]
23 | cr.set_source_rgba(red / 255, green / 255, blue / 255, alpha)
24 |
25 | def set_source_radial(cr, x, y, r0, r1, colors):
26 | if len(colors) > 1:
27 | pat = cairo.RadialGradient(x, y, r0, x, y, r1)
28 | for i in range(len(colors)):
29 | (red, green, blue, alpha) = colors[i]
30 | pat.add_color_stop_rgba(1 / (i + 1), \
31 | red / 255, green / 255, blue / 255, alpha)
32 | cr.set_source(pat)
33 | else:
34 | (red, green, blue, alpha) = colors[0]
35 | cr.set_source_rgba(red / 255, green / 255, blue / 255, alpha)
36 |
37 | def draw_element(cr, x, y, width, height, radius):
38 | degrees = math.pi / 180.0
39 | cr.new_sub_path()
40 | cr.arc(x + width * radius / 100, y + height * radius / 100, \
41 | radius * min(width, height) / 100, -180 * degrees, -90 * degrees)
42 | cr.arc(x + width - width * radius / 100, y + height * radius / 100, \
43 | radius * min(width, height) / 100, -90 * degrees, 0)
44 | cr.arc(x + width - width * radius / 100, y + height - height * radius / 100, \
45 | radius * min(width, height) / 100, 0, 90 * degrees)
46 | cr.arc(x + width * radius / 100, y + height - height * radius / 100, \
47 | radius * min(width, height) / 100, 90 * degrees, -180 * degrees)
48 | cr.close_path()
49 |
50 | def wave(sample, cr, width, height, colors, fill, thickness):
51 | set_source(cr, height, colors)
52 | ls = len(sample)
53 | cr.move_to(0, (1.0 - sample[0]) * height)
54 | for i in range(ls - 1):
55 | height_diff = (sample[i] - sample[i+1])
56 | cr.rel_curve_to(width / (ls - 1) * 0.5, 0.0, \
57 | width / (ls - 1) * 0.5, height_diff * height, \
58 | width / (ls - 1), height_diff * height)
59 | if fill:
60 | cr.line_to(width, height)
61 | cr.line_to(0, height)
62 | cr.fill()
63 | else:
64 | cr.set_line_width(thickness)
65 | cr.stroke()
66 |
67 | def wave_circle(sample, cr, width, height, colors, radius, fill, thickness, inner_circle):
68 | ls = len(sample)
69 | cr.move_to(width / 2, height / 2)
70 | min_radius = min(width, height) * radius / 400
71 | max_radius = min(width, height) / 2
72 | set_source_radial(cr, width / 2, height / 2, min_radius, \
73 | max_radius, colors)
74 | if inner_circle:
75 | cr.rectangle(0, 0, width, height)
76 | cr.arc_negative(width / 2, height / 2, min_radius - thickness / 2, \
77 | 2 * math.pi, 0)
78 | cr.clip()
79 | cr.new_path()
80 | cr.move_to(width / 2 + min_radius, height / 2)
81 | cr.arc(width / 2, height / 2, min_radius, 0, 2 * math.pi)
82 | cr.set_line_width(thickness)
83 | cr.stroke()
84 | min_radius += thickness
85 | cr.move_to( \
86 | width / 2 + math.cos(2 * math.pi / ls * (ls - 0.5) - 0.5 * math.pi) * \
87 | (min_radius + sample[-1] * (max_radius - min_radius)), \
88 | height / 2 + math.sin(2 * math.pi / ls * (ls - 0.5) - 0.5 * math.pi) * \
89 | (min_radius + sample[-1] * (max_radius - min_radius))
90 | )
91 | cr.curve_to( \
92 | width / 2 + math.cos(2 * math.pi / ls * (0) - 0.5 * math.pi) * \
93 | (min_radius + sample[-1] * (max_radius - min_radius)), \
94 | height / 2 + math.sin(2 * math.pi / ls * (0) - 0.5 * math.pi) * \
95 | (min_radius + sample[-1] * (max_radius - min_radius)), \
96 | width / 2 + math.cos(2 * math.pi / ls * (0) - 0.5 * math.pi) * \
97 | (min_radius + sample[0] * (max_radius - min_radius)), \
98 | height / 2 + math.sin(2 * math.pi / ls * (0) - 0.5 * math.pi) * \
99 | (min_radius + sample[0] * (max_radius - min_radius)), \
100 | width / 2 + math.cos(2 * math.pi / ls * (0.5) - 0.5 * math.pi) * \
101 | (min_radius + sample[0] * (max_radius - min_radius)), \
102 | height / 2 + math.sin(2 * math.pi / ls * (0.5) - 0.5 * math.pi) * \
103 | (min_radius + sample[0] * (max_radius - min_radius))
104 | )
105 | for i in range(ls - 1):
106 | cr.curve_to( \
107 | width / 2 + math.cos(2 * math.pi / ls * (i + 1) - 0.5 * math.pi) * \
108 | (min_radius + sample[i] * (max_radius - min_radius)), \
109 | height / 2 + math.sin(2 * math.pi / ls * (i + 1) - 0.5 * math.pi) * \
110 | (min_radius + sample[i] * (max_radius - min_radius)), \
111 | width / 2 + math.cos(2 * math.pi / ls * (i + 1) - 0.5 * math.pi) * \
112 | (min_radius + sample[i+1] * (max_radius - min_radius)), \
113 | height / 2 + math.sin(2 * math.pi / ls * (i + 1) - 0.5 * math.pi) * \
114 | (min_radius + sample[i+1] * (max_radius - min_radius)), \
115 | width / 2 + math.cos(2 * math.pi / ls * (i + 1.5) - 0.5 * math.pi) * \
116 | (min_radius + sample[i+1] * (max_radius - min_radius)), \
117 | height / 2 + math.sin(2 * math.pi / ls * (i + 1.5) - 0.5 * math.pi) * \
118 | (min_radius + sample[i+1] * (max_radius - min_radius))
119 | )
120 | cr.close_path() # required to avoid artifact with thick lines
121 | cr.set_line_width(thickness)
122 |
123 | cr.fill() if fill else cr.stroke()
124 |
125 |
126 | def levels(sample, cr, width, height, colors, offset, radius, fill, thickness):
127 | set_source(cr, height, colors)
128 | ls = len(sample)
129 | step = width / ls
130 | offset_px = step * offset / 100
131 | if fill:
132 | thickness = 0
133 | else:
134 | thickness = min(thickness, \
135 | (step - offset_px * 2) / 2,
136 | (height / 10 - offset_px) / 2)
137 | cr.set_line_width(thickness)
138 | for i in range(ls):
139 | q = int(round(sample[i], 1) * 10)
140 | for r in range(q):
141 | draw_element(cr, step * i + offset_px + thickness / 2, \
142 | height - (height / 10 * (r + 1)) + offset_px / 2 + thickness / 2, \
143 | max(step - offset_px * 2 - thickness, 1), \
144 | max(height / 10 - offset_px - thickness, 1), radius)
145 | cr.fill() if fill else cr.stroke()
146 |
147 | def particles(sample, cr, width, height, colors, offset, radius, fill, thickness):
148 | set_source(cr, height, colors)
149 | ls = len(sample)
150 | step = width / ls
151 | offset_px = step * offset / 100
152 | if fill:
153 | thickness = 0
154 | else:
155 | thickness = min(thickness, \
156 | (step - offset_px * 2) / 2,
157 | (height / 10) / 2)
158 | cr.set_line_width(thickness)
159 | for i in range(ls):
160 | draw_element(cr, step * i + offset_px + thickness / 2, \
161 | height * 0.9 - height * 0.9 * sample[i] + thickness / 2, \
162 | max(step - offset_px * 2 - thickness, 1), \
163 | max(height / 10 - thickness, 1), radius)
164 | cr.fill() if fill else cr.stroke()
165 |
166 | def spine(sample, cr, width, height, colors, offset, radius, fill, thickness):
167 | ls = len(sample)
168 | cr.set_line_width(thickness)
169 | step = width / ls
170 | for i in range(ls):
171 | set_source(cr, height, colors, sample[i] - 0.45)
172 | offset_px = step * offset / 100 * sample[i]
173 | if not fill:
174 | offset_px += thickness / 2
175 | draw_element(cr, \
176 | step * i + step / 2 - sample[i] * step / 2 + offset_px, \
177 | height / 2 - sample[i] * step / 2 + offset_px, \
178 | step * sample[i] - offset_px * 2, \
179 | step * sample[i] - offset_px * 2, radius)
180 | cr.fill() if fill else cr.stroke()
181 |
182 | def bars(sample, cr, width, height, colors, offset, fill, thickness):
183 | set_source(cr, height, colors)
184 | ls = len(sample)
185 | step = width / ls
186 | offset_px = step * offset / 100
187 | if fill:
188 | thickness = 0
189 | else:
190 | thickness = min(thickness, (step - offset_px * 2) / 2)
191 | cr.set_line_width(thickness)
192 | for i in range(ls):
193 | cr.rectangle(step * i + offset_px + thickness / 2, \
194 | height - height * sample[i] + thickness / 2, \
195 | max(step - offset_px * 2 - thickness, 1), height * sample[i] - thickness)
196 | cr.fill() if fill else cr.stroke()
197 |
198 | def bars_circle(sample, cr, width, height, colors, offset, fill, thickness, \
199 | radius):
200 | ls = len(sample)
201 | min_radius = min(width, height) * radius / 400
202 | max_radius = min(width, height) / 2
203 | set_source_radial(cr, width / 2, height / 2, min_radius, max_radius, colors)
204 | if fill:
205 | thickness = 0
206 | thickness_inner_angle = 0
207 | else:
208 | thickness = min(thickness, \
209 | min_radius * math.pi * 2 / ls * 0.25)
210 | cr.set_line_width(thickness)
211 | thickness_inner_angle = math.asin(math.sin(0.25 * math.pi) / \
212 | math.sqrt(min_radius ** 2 + (thickness / 2) ** 2 - 2 * \
213 | min_radius * thickness / 2 * math.cos(0.25 * math.pi)) * \
214 | thickness / 2)
215 | for i in range(ls):
216 | cr.move_to(width / 2 + math.cos( \
217 | 2 * math.pi / ls * (i + offset / 100) - 0.5 * math.pi + \
218 | thickness_inner_angle) * (min_radius + thickness / 2),
219 | height / 2 + math.sin( \
220 | 2 * math.pi / ls * (i + offset / 100) - 0.5 * math.pi + \
221 | thickness_inner_angle) * (min_radius + thickness / 2))
222 | cr.line_to(width / 2 + math.cos( \
223 | 2 * math.pi / ls * (i + offset / 100) - 0.5 * math.pi + \
224 | math.asin(math.sin(0.25 * math.pi) / math.sqrt((min_radius + \
225 | max(sample[i] * (max_radius - min_radius), thickness * 2)) ** 2 + \
226 | (thickness / 2) ** 2 - 2 * (min_radius + sample[i] * \
227 | (max_radius - min_radius)) * thickness / 2 * \
228 | math.cos(0.25 * math.pi)) * thickness / 2)) * \
229 | (min_radius + max(sample[i] * \
230 | (max_radius - min_radius), thickness * 2) - thickness / 2), \
231 | height / 2 + math.sin( \
232 | 2 * math.pi / ls * (i + offset / 100) - 0.5 * math.pi + \
233 | math.asin(math.sin(0.25 * math.pi) / math.sqrt((min_radius + \
234 | max(sample[i] * (max_radius - min_radius), thickness * 2)) ** 2 + \
235 | (thickness / 2) ** 2 - 2 * (min_radius + sample[i] * \
236 | (max_radius - min_radius)) * thickness / 2 * \
237 | math.cos(0.25 * math.pi)) * thickness / 2)) * \
238 | (min_radius + max(sample[i] * \
239 | (max_radius - min_radius), thickness * 2) - thickness / 2))
240 | cr.line_to(width / 2 + math.cos( \
241 | 2 * math.pi / ls * (i + 1 - offset / 100) - 0.5 * math.pi - \
242 | math.asin(math.sin(0.25 * math.pi) / math.sqrt((min_radius + \
243 | max(sample[i] * (max_radius - min_radius), thickness * 2)) ** 2 + \
244 | (thickness / 2) ** 2 - 2 * (min_radius + max(sample[i] * \
245 | (max_radius - min_radius), thickness * 2)) * thickness / 2 * \
246 | math.cos(0.25 * math.pi)) * thickness / 2)) * \
247 | (min_radius + max(sample[i] * \
248 | (max_radius - min_radius), thickness * 2) - thickness / 2), \
249 | height / 2 + math.sin( \
250 | 2 * math.pi / ls * (i + 1 - offset / 100) - 0.5 * math.pi - \
251 | math.asin(math.sin(0.25 * math.pi) / math.sqrt((min_radius + \
252 | max(sample[i] * (max_radius - min_radius), thickness * 2)) ** 2 + \
253 | (thickness / 2) ** 2 - 2 * (min_radius + max(sample[i] * \
254 | (max_radius - min_radius), thickness * 2)) * thickness / 2 * \
255 | math.cos(0.25 * math.pi)) * thickness / 2)) * \
256 | (min_radius + max(sample[i] * \
257 | (max_radius - min_radius), thickness * 2) - thickness / 2))
258 | cr.line_to(width / 2 + math.cos( \
259 | 2 * math.pi / ls * (i + 1 - offset / 100) - 0.5 * math.pi - \
260 | thickness_inner_angle) * (min_radius + thickness / 2), \
261 | height / 2 + math.sin( \
262 | 2 * math.pi / ls * (i + 1 - offset / 100) - 0.5 * math.pi - \
263 | thickness_inner_angle) * (min_radius + thickness / 2))
264 | cr.close_path()
265 | cr.fill() if fill else cr.stroke()
266 |
--------------------------------------------------------------------------------
/src/drawing_area.py:
--------------------------------------------------------------------------------
1 | # drawing_area.py
2 | #
3 | # Copyright (c) 2023, TheWisker
4 | # All rights reserved.
5 | #
6 | # This source code is licensed under the BSD-style license found in the
7 | # LICENSE file in the root directory of this source tree.
8 |
9 | from gi.repository import Gtk, GObject
10 | from threading import Thread
11 | from cavasik.cava import Cava
12 | from cavasik.draw_functions import *
13 | from cavasik.settings import CavasikSettings
14 |
15 | class CavasikDrawingArea(Gtk.DrawingArea):
16 | __gtype_name__ = 'CavasikDrawingArea'
17 |
18 | def __init__(self, settings, **kwargs):
19 | super().__init__(**kwargs)
20 |
21 | def new():
22 | cda = Gtk.DrawingArea.new()
23 | cda.__class__ = CavasikDrawingArea
24 | cda.set_vexpand(True)
25 | cda.set_hexpand(True)
26 | cda.set_draw_func(cda.draw_func, None, None)
27 | cda.cava = None
28 | cda.spinner = None
29 | cda.settings = CavasikSettings.new(cda.on_settings_changed)
30 | cda.connect('unrealize', cda.on_unrealize)
31 | cda.timeout = None
32 | cda.fps = 60
33 | return cda
34 |
35 | def run(self):
36 | self.fps = self.settings['fps']
37 | self.on_settings_changed(None)
38 | if self.cava == None:
39 | self.cava = Cava()
40 | self.cava_thread = Thread(target=self.cava.run)
41 | self.cava_thread.start()
42 | if self.spinner != None:
43 | self.spinner.set_visible(False)
44 | if self.timeout:
45 | GObject.source_remove(self.timeout)
46 | self.cava_sample = self.cava.sample
47 | self.timeout = GObject.timeout_add(1000.0 / self.fps, self.redraw)
48 |
49 | def on_settings_changed(self, key):
50 | self.draw_mode = self.settings['mode']
51 | self.mirror = self.settings['mirror']
52 | self.mirror_offset = self.settings['mirror-offset']
53 | self.mirror_opacity = self.settings['mirror-opacity'] / 100
54 | self.mirror_clones = self.settings['mirror-clones']
55 | self.mirror_ratio = self.settings['mirror-ratio']
56 | self.circle = self.settings['circle']
57 | self.wave_inner_circle = self.settings['wave-inner-circle']
58 | self.radius = self.settings['radius']
59 | self.direction = self.settings['direction']
60 | self.set_margin_top(self.settings['margin'])
61 | self.set_margin_bottom(self.settings['margin'])
62 | self.set_margin_start(self.settings['margin'])
63 | self.set_margin_end(self.settings['margin'])
64 | self.offset = self.settings['items-offset']
65 | self.roundness = self.settings['items-roundness']
66 | self.thickness = self.settings['line-thickness']
67 | self.fill = self.settings['fill']
68 | self.reverse_order = self.settings['reverse-order']
69 | self.channels = self.settings['channels']
70 | self.color_animation = self.settings['color-animation']
71 | self.color_animation_length = self.settings['color-animation-length']
72 | try:
73 | color_profile = self.settings['color-profiles'][ \
74 | self.settings['active-color-profile']]
75 | self.colors = color_profile[1]
76 | except:
77 | self.colors = []
78 |
79 | if len(self.colors) == 0:
80 | self.settings['color-profiles'] = [(_('Default'), \
81 | [(53, 132, 228, 1.0)], [])]
82 | return
83 |
84 | try:
85 | if self.settings['mirror-sync']:
86 | self.mirror_colors = self.colors
87 | else:
88 | color_profile = self.settings['color-profiles'][ \
89 | self.settings['mirror-colors']]
90 | self.mirror_colors = color_profile[1]
91 | except:
92 | self.settings['mirror-colors'] = 0
93 | return
94 | try:
95 | color_profile = self.settings['color-profiles'][ \
96 | self.settings['color-animation-target']]
97 | self.color_animation_target = color_profile[1]
98 | except:
99 | self.settings['color-animation-target'] = 0
100 | return
101 | try:
102 | color_profile = self.settings['color-profiles'][ \
103 | self.settings['color-animation-mirror-target']]
104 | self.color_animation_mirror_target = color_profile[1]
105 | except:
106 | self.settings['color-animation-mirror-target'] = 0
107 | return
108 | self.animation_count = 0
109 | self.animation_vector = []
110 | self.mirror_animation_vector = []
111 | if self.color_animation:
112 | self.animation_frames = self.fps * self.color_animation_length
113 | for c, tc in zip(self.colors, self.color_animation_target):
114 | self.animation_vector.append(((tc[0] - c[0])/self.animation_frames, (tc[1] - c[1])/self.animation_frames, \
115 | (tc[2] - c[2])/self.animation_frames, (tc[3] - c[3])/self.animation_frames))
116 | for c, tc in zip(self.colors, self.color_animation_mirror_target):
117 | self.mirror_animation_vector.append(((tc[0] - c[0])/self.animation_frames, (tc[1] - c[1])/self.animation_frames, \
118 | (tc[2] - c[2])/self.animation_frames, (tc[3] - c[3])/self.animation_frames))
119 |
120 | if key in ('bars', 'autosens', 'sensitivity', 'channels', \
121 | 'smoothing', 'noise-reduction'):
122 | if not self.cava.restarting:
123 | self.cava.stop()
124 | self.cava.restarting = True
125 | if self.spinner != None:
126 | self.spinner.set_visible(True)
127 | self.cava.sample = []
128 | GObject.timeout_add_seconds(3, self.run)
129 |
130 | def draw_func(self, area, cr, width, height, _, mirror_call):
131 | if len(self.cava_sample) > 0:
132 | if not mirror_call:
133 | if self.color_animation:
134 | self.animation_count += 1
135 | for i, (c, vc) in enumerate(zip(self.colors, self.animation_vector)):
136 | self.colors = self.colors[:i] + [(c[0] + vc[0], c[1] + vc[1], \
137 | c[2] + vc[2], c[3] + vc[3])] + self.colors[i+1:]
138 | for i, (c, vc) in enumerate(zip(self.mirror_colors, self.mirror_animation_vector)):
139 | self.mirror_colors = self.mirror_colors[:i] + [(c[0] + vc[0], c[1] + vc[1], \
140 | c[2] + vc[2], c[3] + vc[3])] + self.mirror_colors[i+1:]
141 | if self.animation_count >= self.animation_frames:
142 | self.animation_count = 0
143 | for i, vc in enumerate(self.animation_vector):
144 | self.animation_vector = self.animation_vector[:i] + [(-vc[0], -vc[1], -vc[2], -vc[3])] + self.animation_vector[i+1:]
145 | for i, vc in enumerate(self.mirror_animation_vector):
146 | self.mirror_animation_vector = self.mirror_animation_vector[:i] + [(-vc[0], -vc[1], -vc[2], -vc[3])] + self.mirror_animation_vector[i+1:]
147 |
148 | if self.direction == 'left-right':
149 | cr.rotate(0.5*math.pi)
150 | cr.translate(0, -width)
151 | width, height = height, width
152 | elif self.direction == 'top-bottom':
153 | cr.rotate(math.pi)
154 | cr.translate(-width, -height)
155 | elif self.direction == 'right-left':
156 | cr.rotate(1.5*math.pi)
157 | cr.translate(-height, 0)
158 | width, height = height, width
159 |
160 | if not self.circle and not self.draw_mode == 'spine':
161 | if self.mirror == 'normal':
162 | cr.scale(1, -0.5*(1-self.mirror_offset/2))
163 | cr.translate(0, (1/(-0.5*(1-self.mirror_offset/2)))*height+(self.mirror_offset*(1/(-0.5*(1-self.mirror_offset/2)))*height/10))
164 | prev_colors = [c for c in self.colors]
165 | self.colors = [c for c in self.mirror_colors]
166 | if not self.mirror_opacity == 1:
167 | for c,color in enumerate(self.colors):
168 | self.colors[c] = (self.colors[c][0], self.colors[c][1], self.colors[c][2], self.mirror_opacity)
169 | self.draw_func(area, cr, width, height, _, True)
170 | self.colors = prev_colors
171 | cr.scale(1, -1)
172 | cr.translate(0, (1/(-0.5*(1-self.mirror_offset/2)))*height+(self.mirror_offset*(1/(-0.5*(1-self.mirror_offset/2)))*height/5)+1)
173 | elif self.mirror == 'inverted':
174 | cr.scale(1, -0.5)
175 | cr.translate(0, -height)
176 | prev_colors = [c for c in self.colors]
177 | self.colors = [c for c in self.mirror_colors]
178 | self.draw_func(area, cr, width, height, _, True)
179 | self.colors = prev_colors
180 | cr.scale(1, -1)
181 | elif self.mirror == 'overlapping':
182 | self.draw_func(area, cr, width, height, _, True)
183 | for i in range(1, self.mirror_clones+1):
184 | cr.scale(1, self.mirror_ratio**i)
185 | cr.translate(0, (1 - self.mirror_ratio**i)/(self.mirror_ratio**i) * height)
186 | if i % 2 == 1:
187 | prev_colors = [c for c in self.colors]
188 | self.colors = [c for c in self.mirror_colors]
189 | self.draw_func(area, cr, width, height, _, True)
190 | if i % 2 == 1:
191 | self.colors = prev_colors
192 |
193 | if self.draw_mode == 'wave':
194 | if self.circle:
195 | wave_circle(self.cava_sample, cr, width, height, \
196 | self.colors, self.radius, self.fill, self.thickness, \
197 | self.wave_inner_circle)
198 | else:
199 | wave(self.cava_sample, cr, width, height, self.colors, \
200 | self.fill, self.thickness)
201 | elif self.draw_mode == 'levels':
202 | levels(self.cava_sample, cr, width, height, self.colors, \
203 | self.offset, self.roundness, self.fill, self.thickness)
204 | elif self.draw_mode == 'particles':
205 | particles(self.cava_sample, cr, width, height, self.colors, \
206 | self.offset, self.roundness, self.fill, self.thickness)
207 | elif self.draw_mode == 'spine':
208 | spine(self.cava_sample, cr, width, height, self.colors, \
209 | self.offset, self.roundness, self.fill, self.thickness)
210 | elif self.draw_mode == 'bars':
211 | if self.circle:
212 | bars_circle(self.cava_sample, cr, width, height, \
213 | self.colors, self.offset, self.fill, \
214 | self.thickness, self.radius)
215 | else:
216 | bars(self.cava_sample, cr, width, height, self.colors, \
217 | self.offset, self.fill, self.thickness)
218 |
219 |
220 | def redraw(self):
221 | self.cava_sample = self.cava.sample
222 | if self.reverse_order:
223 | if self.channels == 'mono':
224 | self.cava_sample = self.cava_sample[::-1]
225 | else:
226 | self.cava_sample = \
227 | self.cava_sample[0:int(len(self.cava_sample)/2):][::-1] + \
228 | self.cava_sample[int(len(self.cava_sample)/2)::][::-1]
229 | self.queue_draw()
230 | return True
231 |
232 | def on_unrealize(self, obj):
233 | self.cava.stop()
234 |
--------------------------------------------------------------------------------
/src/main.py:
--------------------------------------------------------------------------------
1 | # main.py
2 | #
3 | # Copyright (c) 2023, TheWisker
4 | # All rights reserved.
5 | #
6 | # This source code is licensed under the BSD-style license found in the
7 | # LICENSE file in the root directory of this source tree.
8 |
9 | import sys
10 | import gi
11 |
12 | gi.require_version('Gtk', '4.0')
13 | gi.require_version('Adw', '1')
14 |
15 | from gi.repository import Gtk, Gio, Adw
16 | from .window import CavasikWindow
17 | from .preferences_window import CavasikPreferencesWindow
18 | from .translation_credits import get_translation_credits
19 |
20 | from .dbus import BusInterface
21 |
22 | class CavasikApplication(Adw.Application):
23 | """The main application singleton class"""
24 |
25 | def __init__(self, version):
26 | super().__init__(application_id='io.github.TheWisker.Cavasik',
27 | flags=Gio.ApplicationFlags.FLAGS_NONE)
28 | self.version = version
29 | self.create_action('about', self.on_about_action)
30 | self.create_action('open-menu', self.on_menu_action, ['F10'])
31 | self.create_action('toggle-fullscreen', self.on_fullscreen_action, ['F11'])
32 | self.create_action('preferences', self.on_preferences_action,
33 | ['P'])
34 | self.create_action('shortcuts', self.on_shortcuts_action, \
35 | ['H'])
36 | self.create_action('close', self.on_close_action, ['w'])
37 | self.create_action('quit', self.on_quit_action, ['q'])
38 | self.bus_interface = BusInterface()
39 |
40 | def do_activate(self):
41 | """Called when the application is activated.
42 |
43 | We raise the application's main window, creating it if
44 | necessary.
45 | """
46 | self.win = self.props.active_window
47 | if not self.win:
48 | self.win = CavasikWindow(application=self)
49 | self.win.present()
50 |
51 | def on_about_action(self, *args):
52 | """Callback for the app.about action."""
53 | about = Adw.AboutWindow(transient_for=self.props.active_window,
54 | application_name='Cavasik',
55 | application_icon='io.github.TheWisker.Cavasik',
56 | developer_name=_('TheWisker'),
57 | version=self.version,
58 | developers=[_('TheWisker')],
59 | copyright='© 2023 TheWisker',
60 | website='https://github.com/TheWisker/Cavasik',
61 | issue_url='https://github.com/TheWisker/Cavasik/issues',
62 | license_type=Gtk.License.GPL_3_0,
63 | translator_credits=get_translation_credits())
64 | about.present()
65 |
66 | def on_preferences_action(self, widget, _):
67 | self.pref_win = None
68 | for w in self.get_windows():
69 | if type(w) == CavasikPreferencesWindow:
70 | self.pref_win = w
71 | break
72 | if not self.pref_win:
73 | self.pref_win = CavasikPreferencesWindow(application=self)
74 | self.pref_win.present()
75 |
76 | def on_shortcuts_action(self, widget, _):
77 | self.shortcuts_win = None
78 | for w in self.get_windows():
79 | if type(w) == Gtk.ShortcutsWindow:
80 | self.shortcuts_win = w
81 | break
82 | if not self.shortcuts_win:
83 | builder = Gtk.Builder.new_from_resource( \
84 | '/io/github/TheWisker/Cavasik/shortcuts_dialog.ui')
85 | self.shortcuts_win = builder.get_object('dialog')
86 | self.shortcuts_win.present()
87 |
88 | def on_quit_action(self, widget, _):
89 | self.win.close()
90 | self.quit()
91 |
92 | def on_close_action(self, widget, _):
93 | win = self.props.active_window
94 | win.close()
95 | if type(win) == CavasikWindow:
96 | self.quit()
97 |
98 | def on_menu_action(self, widget, _):
99 | self.win.menu_button.activate()
100 |
101 | def on_fullscreen_action(self, widget, _):
102 | self.win.unfullscreen() if self.win.is_fullscreen() else self.win.fullscreen()
103 |
104 | def create_action(self, name, callback, shortcuts=None):
105 | """Add an application action.
106 |
107 | Args:
108 | name: the name of the action
109 | callback: the function to be called when the action is
110 | activated
111 | shortcuts: an optional list of accelerators
112 | """
113 | action = Gio.SimpleAction.new(name, None)
114 | action.connect("activate", callback)
115 | self.add_action(action)
116 | if shortcuts:
117 | self.set_accels_for_action(f"app.{name}", shortcuts)
118 |
119 |
120 | def main(version):
121 | """The application's entry point."""
122 | app = CavasikApplication(version)
123 | return app.run(sys.argv)
124 |
--------------------------------------------------------------------------------
/src/meson.build:
--------------------------------------------------------------------------------
1 | gnome = import('gnome')
2 | python = import('python')
3 |
4 | moduledir = join_paths(pkgdatadir, 'cavasik')
5 |
6 | cavasik_sources = [
7 | '__init__.py',
8 | 'main.py',
9 | 'window.py',
10 | 'cava.py',
11 | 'drawing_area.py',
12 | 'draw_functions.py',
13 | 'settings.py',
14 | 'settings_import_export.py',
15 | 'preferences_window.py',
16 | 'shortcuts.py',
17 | 'dbus.py'
18 | ]
19 |
20 | conf = configuration_data()
21 | conf.set('PYTHON', python.find_installation('python3').path())
22 | conf.set('VERSION', meson.project_version())
23 | conf.set('localedir', join_paths(get_option('prefix'), get_option('localedir')))
24 | conf.set('pkgdatadir', pkgdatadir)
25 |
26 | install_data(cavasik_sources, install_dir: moduledir)
27 | gnome.compile_resources('cavasik', 'cavasik.gresource.xml', gresource_bundle: true, install: true, install_dir: pkgdatadir)
28 | configure_file(input: 'cavasik.in', output: 'cavasik', configuration: conf, install: true, install_dir: get_option('bindir'))
29 | configure_file(input: 'translation_credits.py.in', output: 'translation_credits.py', configuration: conf, install: true, install_dir: moduledir)
--------------------------------------------------------------------------------
/src/settings.py:
--------------------------------------------------------------------------------
1 | # settings.py
2 | #
3 | # Copyright (c) 2023, TheWisker
4 | # All rights reserved.
5 | #
6 | # This source code is licensed under the BSD-style license found in the
7 | # LICENSE file in the root directory of this source tree.
8 |
9 | from gi.repository import Gio, GLib
10 | from inspect import signature
11 |
12 | class CavasikSettings(Gio.Settings):
13 | __gtype_name__ = 'CavasikSettings'
14 |
15 | def __init__(self):
16 | super().__init__(self)
17 |
18 | def new(callback_fn=None):
19 | gsettings = Gio.Settings.new('io.github.TheWisker.Cavasik')
20 | gsettings.__class__ = CavasikSettings
21 | if callback_fn:
22 | gsettings.connect('changed', gsettings.on_settings_changed)
23 | gsettings.callback_fn = callback_fn
24 | gsettings.callback_fn_sig_len = len(signature(callback_fn).parameters)
25 | return gsettings
26 |
27 | def on_settings_changed(self, obj, key):
28 | if self.callback_fn_sig_len > 0:
29 | self.callback_fn(key)
30 | else:
31 | self.callback_fn()
32 |
33 |
--------------------------------------------------------------------------------
/src/settings_import_export.py:
--------------------------------------------------------------------------------
1 | # settings_import_export.py
2 | #
3 | # Copyright (c) 2023, TheWisker
4 | # All rights reserved.
5 | #
6 | # This source code is licensed under the BSD-style license found in the
7 | # LICENSE file in the root directory of this source tree.
8 |
9 | import subprocess
10 | from gi.repository import Adw
11 |
12 | def import_settings(window, path):
13 | try:
14 | with open(path, 'r') as file:
15 | lines = file.readlines()
16 | for line in lines:
17 | if line != '\n':
18 | subprocess.run(['gsettings', 'set', \
19 | 'io.github.TheWisker.Cavasik', line.split(' ')[0], \
20 | line.replace(line.split(' ')[0], '').strip()])
21 | toast_msg = _('Settings successfully imported')
22 |
23 | except Exception as e:
24 | print('Can\'t import settings from file: ' + path)
25 | print(e)
26 | toast_msg = _('Failed to import settings')
27 |
28 | window.add_toast(Adw.Toast.new(toast_msg))
29 |
30 |
31 | def export_settings(window, path):
32 | gsettings_list = subprocess.run( \
33 | ['gsettings', 'list-recursively', 'io.github.TheWisker.Cavasik'], \
34 | stdout=subprocess.PIPE).stdout.decode('utf-8')
35 | try:
36 | with open(path, 'w') as file:
37 | for line in gsettings_list.split('\n'):
38 | file.write(' '.join(line.split(' ')[1::]) + '\n')
39 | toast_msg = _('File successfully saved')
40 |
41 | except Exception as e:
42 | print('Can\'t export settings to file: ' + path)
43 | print(e)
44 | toast_msg = _('Failed to save file')
45 |
46 | window.add_toast(Adw.Toast.new(toast_msg))
47 |
--------------------------------------------------------------------------------
/src/shortcuts.py:
--------------------------------------------------------------------------------
1 | # shortcuts.py
2 | #
3 | # Copyright (c) 2023, TheWisker
4 | # All rights reserved.
5 | #
6 | # This source code is licensed under the BSD-style license found in the
7 | # LICENSE file in the root directory of this source tree.
8 |
9 | import gi
10 | gi.require_version('Gtk', '4.0')
11 | from gi.repository import Gio, Gtk
12 |
13 | def add_shortcuts(widget, settings):
14 | action_map = Gio.SimpleActionGroup.new()
15 | widget.insert_action_group("cavasik", action_map)
16 | shortcut_controller = Gtk.ShortcutController.new()
17 | shortcut_controller.set_scope(Gtk.ShortcutScope.MANAGED)
18 | widget.add_controller(shortcut_controller)
19 |
20 | act_next_mode = Gio.SimpleAction.new("next-mode", None)
21 | act_next_mode.connect('activate', change_option, settings, "mode", 1)
22 | action_map.add_action(act_next_mode)
23 | shortcut_controller.add_shortcut(Gtk.Shortcut.new( \
24 | Gtk.ShortcutTrigger.parse_string("D"), \
25 | Gtk.NamedAction.new("cavasik.next-mode")))
26 | act_prev_mode = Gio.SimpleAction.new("prev-mode", None)
27 | act_prev_mode.connect('activate', change_option, settings, "mode", -1)
28 | action_map.add_action(act_prev_mode)
29 | shortcut_controller.add_shortcut(Gtk.Shortcut.new( \
30 | Gtk.ShortcutTrigger.parse_string("D"), \
31 | Gtk.NamedAction.new("cavasik.prev-mode")))
32 |
33 | act_cycle_shape = Gio.SimpleAction.new("cycle-shape", None)
34 | act_cycle_shape.connect('activate', toggle_setting, settings, "circle")
35 | action_map.add_action(act_cycle_shape)
36 | shortcut_controller.add_shortcut(Gtk.Shortcut.new( \
37 | Gtk.ShortcutTrigger.parse_string("S"), \
38 | Gtk.NamedAction.new("cavasik.cycle-shape")))
39 |
40 | act_next_mirror = Gio.SimpleAction.new("next-mirror", None)
41 | act_next_mirror.connect('activate', change_option, settings, "mirror", 1)
42 | action_map.add_action(act_next_mirror)
43 | shortcut_controller.add_shortcut(Gtk.Shortcut.new( \
44 | Gtk.ShortcutTrigger.parse_string("M"), \
45 | Gtk.NamedAction.new("cavasik.next-mirror")))
46 | act_prev_mirror = Gio.SimpleAction.new("prev-mirror", None)
47 | act_prev_mirror.connect('activate', change_option, settings, "mirror", -1)
48 | action_map.add_action(act_prev_mirror)
49 | shortcut_controller.add_shortcut(Gtk.Shortcut.new( \
50 | Gtk.ShortcutTrigger.parse_string("M"), \
51 | Gtk.NamedAction.new("cavasik.prev-mirror")))
52 |
53 | act_next_direction = Gio.SimpleAction.new("next-direction", None)
54 | act_next_direction.connect('activate', change_option, settings, "direction", 1)
55 | action_map.add_action(act_next_direction)
56 | shortcut_controller.add_shortcut(Gtk.Shortcut.new( \
57 | Gtk.ShortcutTrigger.parse_string("A"), \
58 | Gtk.NamedAction.new("cavasik.next-direction")))
59 | act_prev_direction = Gio.SimpleAction.new("prev-direction", None)
60 | act_prev_direction.connect('activate', change_option, settings, "direction", -1)
61 | action_map.add_action(act_prev_direction)
62 | shortcut_controller.add_shortcut(Gtk.Shortcut.new( \
63 | Gtk.ShortcutTrigger.parse_string("A"), \
64 | Gtk.NamedAction.new("cavasik.prev-direction")))
65 |
66 | act_toggle_fill = Gio.SimpleAction.new("toggle-fill", None)
67 | act_toggle_fill.connect('activate', toggle_setting, settings, 'fill')
68 | action_map.add_action(act_toggle_fill)
69 | shortcut_controller.add_shortcut(Gtk.Shortcut.new( \
70 | Gtk.ShortcutTrigger.parse_string("F"), \
71 | Gtk.NamedAction.new("cavasik.toggle-fill")))
72 |
73 | act_toggle_dbus_colors = Gio.SimpleAction.new("toggle-dbus-colors", None)
74 | act_toggle_dbus_colors.connect('activate', toggle_setting, settings, \
75 | 'dbus-colors')
76 | action_map.add_action(act_toggle_dbus_colors)
77 | shortcut_controller.add_shortcut(Gtk.Shortcut.new( \
78 | Gtk.ShortcutTrigger.parse_string("T"), \
79 | Gtk.NamedAction.new("cavasik.toggle-dbus-colors")))
80 |
81 | act_toggle_controls = Gio.SimpleAction.new("toggle-controls", None)
82 | act_toggle_controls.connect('activate', toggle_setting, settings, \
83 | 'window-controls')
84 | action_map.add_action(act_toggle_controls)
85 | shortcut_controller.add_shortcut(Gtk.Shortcut.new( \
86 | Gtk.ShortcutTrigger.parse_string("O"), \
87 | Gtk.NamedAction.new("cavasik.toggle-controls")))
88 |
89 | act_inc_bars = Gio.SimpleAction.new("increase-bars", None)
90 | act_inc_bars.connect('activate', change_setting, settings, 'bars', 2)
91 | action_map.add_action(act_inc_bars)
92 | shortcut_controller.add_shortcut(Gtk.Shortcut.new( \
93 | Gtk.ShortcutTrigger.parse_string("B"), \
94 | Gtk.NamedAction.new("cavasik.increase-bars")))
95 | act_dec_bars = Gio.SimpleAction.new("decrease-bars", None)
96 | act_dec_bars.connect('activate', change_setting, settings, 'bars', -2)
97 | action_map.add_action(act_dec_bars)
98 | shortcut_controller.add_shortcut(Gtk.Shortcut.new( \
99 | Gtk.ShortcutTrigger.parse_string("B"), \
100 | Gtk.NamedAction.new("cavasik.decrease-bars")))
101 |
102 | act_inc_sensitivity = Gio.SimpleAction.new("increase-sensitivity", None)
103 | act_inc_sensitivity.connect('activate', change_setting, settings, 'sensitivity', 20)
104 | action_map.add_action(act_inc_sensitivity)
105 | shortcut_controller.add_shortcut(Gtk.Shortcut.new( \
106 | Gtk.ShortcutTrigger.parse_string("V"), \
107 | Gtk.NamedAction.new("cavasik.increase-sensitivity")))
108 | act_dec_sensitivity = Gio.SimpleAction.new("decrease-sensitivity", None)
109 | act_dec_sensitivity.connect('activate', change_setting, settings, 'sensitivity', -20)
110 | action_map.add_action(act_dec_sensitivity)
111 | shortcut_controller.add_shortcut(Gtk.Shortcut.new( \
112 | Gtk.ShortcutTrigger.parse_string("V"), \
113 | Gtk.NamedAction.new("cavasik.decrease-sensitivity")))
114 |
115 | act_toggle_reverse = Gio.SimpleAction.new("toggle-reverse", None)
116 | act_toggle_reverse.connect('activate', toggle_setting, settings, \
117 | 'reverse-order')
118 | action_map.add_action(act_toggle_reverse)
119 | shortcut_controller.add_shortcut(Gtk.Shortcut.new( \
120 | Gtk.ShortcutTrigger.parse_string("E"), \
121 | Gtk.NamedAction.new("cavasik.toggle-reverse")))
122 |
123 | act_toggle_style = Gio.SimpleAction.new("toggle-style", None)
124 | act_toggle_style.connect('activate', change_widgets_style, settings)
125 | action_map.add_action(act_toggle_style)
126 | shortcut_controller.add_shortcut(Gtk.Shortcut.new( \
127 | Gtk.ShortcutTrigger.parse_string("W"), \
128 | Gtk.NamedAction.new("cavasik.toggle-style")))
129 |
130 | act_next_profile = Gio.SimpleAction.new("next-profile", None)
131 | act_next_profile.connect('activate', change_color_profile, settings, "color-profiles", "active-color-profile", 1)
132 | action_map.add_action(act_next_profile)
133 | shortcut_controller.add_shortcut(Gtk.Shortcut.new( \
134 | Gtk.ShortcutTrigger.parse_string("C"), \
135 | Gtk.NamedAction.new("cavasik.next-profile")))
136 | act_prev_profile = Gio.SimpleAction.new("prev-profile", None)
137 | act_prev_profile.connect('activate', change_color_profile, settings, "color-profiles", "active-color-profile", -1)
138 | action_map.add_action(act_prev_profile)
139 | shortcut_controller.add_shortcut(Gtk.Shortcut.new( \
140 | Gtk.ShortcutTrigger.parse_string("C"), \
141 | Gtk.NamedAction.new("cavasik.prev-profile")))
142 |
143 | act_next_mirror_profile = Gio.SimpleAction.new("next-mirror-profile", None)
144 | act_next_mirror_profile.connect('activate', change_color_profile, settings, "color-profiles", "mirror-colors", 1)
145 | action_map.add_action(act_next_mirror_profile)
146 | shortcut_controller.add_shortcut(Gtk.Shortcut.new( \
147 | Gtk.ShortcutTrigger.parse_string("N"), \
148 | Gtk.NamedAction.new("cavasik.next-mirror-profile")))
149 | act_prev_mirror_profile = Gio.SimpleAction.new("prev-mirror-profile", None)
150 | act_prev_mirror_profile.connect('activate', change_color_profile, settings, "color-profiles", "mirror-colors", -1)
151 | action_map.add_action(act_prev_mirror_profile)
152 | shortcut_controller.add_shortcut(Gtk.Shortcut.new( \
153 | Gtk.ShortcutTrigger.parse_string("N"), \
154 | Gtk.NamedAction.new("cavasik.prev-mirror-profile")))
155 |
156 | act_toggle_color_animation = Gio.SimpleAction.new("toggle-color-animation", None)
157 | act_toggle_color_animation.connect('activate', toggle_setting, settings, \
158 | 'color-animation')
159 | action_map.add_action(act_toggle_color_animation)
160 | shortcut_controller.add_shortcut(Gtk.Shortcut.new( \
161 | Gtk.ShortcutTrigger.parse_string("G"), \
162 | Gtk.NamedAction.new("cavasik.toggle-color-animation")))
163 |
164 | act_next_target_profile = Gio.SimpleAction.new("next-target-profile", None)
165 | act_next_target_profile.connect('activate', change_color_profile, settings, "color-profiles", "color-animation-target", 1)
166 | action_map.add_action(act_next_target_profile)
167 | shortcut_controller.add_shortcut(Gtk.Shortcut.new( \
168 | Gtk.ShortcutTrigger.parse_string("J"), \
169 | Gtk.NamedAction.new("cavasik.next-target-profile")))
170 | act_prev_target_profile = Gio.SimpleAction.new("prev-target-profile", None)
171 | act_prev_target_profile.connect('activate', change_color_profile, settings, "color-profiles", "color-animation-target", -1)
172 | action_map.add_action(act_prev_target_profile)
173 | shortcut_controller.add_shortcut(Gtk.Shortcut.new( \
174 | Gtk.ShortcutTrigger.parse_string("J"), \
175 | Gtk.NamedAction.new("cavasik.prev-target-profile")))
176 |
177 | act_next_mirror_target_profile = Gio.SimpleAction.new("next-mirror-target-profile", None)
178 | act_next_mirror_target_profile.connect('activate', change_color_profile, settings, "color-profiles", "color-animation-mirror-target", 1)
179 | action_map.add_action(act_next_mirror_target_profile)
180 | shortcut_controller.add_shortcut(Gtk.Shortcut.new( \
181 | Gtk.ShortcutTrigger.parse_string("K"), \
182 | Gtk.NamedAction.new("cavasik.next-mirror-target-profile")))
183 | act_prev_mirror_target_profile = Gio.SimpleAction.new("prev-mirror-target-profile", None)
184 | act_prev_mirror_target_profile.connect('activate', change_color_profile, settings, "color-profiles", "color-animation-mirror-target", -1)
185 | action_map.add_action(act_prev_mirror_target_profile)
186 | shortcut_controller.add_shortcut(Gtk.Shortcut.new( \
187 | Gtk.ShortcutTrigger.parse_string("K"), \
188 | Gtk.NamedAction.new("cavasik.prev-mirror-target-profile")))
189 |
190 | def change_option(action, parameter, settings, key, diff):
191 | modes = settings.get_range(key)[1]
192 | new_index = modes.index(settings[key]) + diff
193 | if new_index > len(modes) - 1:
194 | new_index = 0
195 | elif new_index < 0:
196 | new_index = len(modes) - 1
197 | settings[key] = modes[new_index]
198 |
199 | def change_widgets_style(action, parameter, settings):
200 | if settings['widgets-style'] == 'light':
201 | settings['widgets-style'] = 'dark'
202 | else:
203 | settings['widgets-style'] = 'light'
204 |
205 | def change_color_profile(action, parameter, settings, masterkey, key, diff):
206 | profiles = settings[masterkey]
207 | new_index = settings[key] + diff
208 | if new_index > len(profiles) - 1:
209 | new_index = 0
210 | elif new_index < 0:
211 | new_index = len(profiles) - 1
212 | settings[key] = new_index
213 |
214 | def change_setting(action, parameter, settings, key, diff):
215 | try:
216 | settings[key] += diff
217 | except:
218 | pass
219 |
220 | def toggle_setting(action, parameter, settings, key):
221 | settings[key] = not settings[key]
222 |
--------------------------------------------------------------------------------
/src/shortcuts_dialog.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | false
5 | true
6 | true
7 | false
8 |
9 |
10 |
11 |
12 | Cavasik
13 |
14 |
15 | Change Shape Mode
16 | S
17 |
18 |
19 |
20 |
21 | Change Drawing Mode
22 | D <Shift>D
23 |
24 |
25 |
26 |
27 | Change Mirror Mode
28 | M <Shift>M
29 |
30 |
31 |
32 |
33 | Change Direction
34 | A <Shift>A
35 |
36 |
37 |
38 |
39 | Toggle Filling
40 | F
41 |
42 |
43 |
44 |
45 | Toggle DBus Colors
46 | T
47 |
48 |
49 |
50 |
51 | Toggle Window Controls
52 | O
53 |
54 |
55 |
56 |
57 |
58 |
59 | CAVA
60 |
61 |
62 | Change Number of Bars
63 | B <Shift>B
64 |
65 |
66 |
67 |
68 | Increase/Decrease Sensitivity
69 | V <Shift>V
70 |
71 |
72 |
73 |
74 | Toggle Reverse Order
75 | E
76 |
77 |
78 |
79 |
80 |
81 |
82 | Colors
83 |
84 |
85 | Toggle Widgets Style
86 | W
87 |
88 |
89 |
90 |
91 | Change Colors Profile
92 | C <Shift>C
93 |
94 |
95 |
96 |
97 | Change Mirror Profile
98 | N <Shift>N
99 |
100 |
101 |
102 |
103 | Toggle Color Animation
104 | G
105 |
106 |
107 |
108 |
109 | Change Target Profile
110 | J <Shift>J
111 |
112 |
113 |
114 |
115 | Change Mirror Target Profile
116 | K <Shift>K
117 |
118 |
119 |
120 |
121 |
122 |
123 | Window
124 |
125 |
126 | Open Menu
127 | F10
128 |
129 |
130 |
131 |
132 | Toggle Fullscreen
133 | F11
134 |
135 |
136 |
137 |
138 | Preferences
139 | P
140 |
141 |
142 |
143 |
144 | Show Shortcuts
145 | H
146 |
147 |
148 |
149 |
150 | Close Active Window
151 | <Ctrl>W
152 |
153 |
154 |
155 |
156 | Quit
157 | <Ctrl>Q
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
--------------------------------------------------------------------------------
/src/translation_credits.py.in:
--------------------------------------------------------------------------------
1 | # translation_credits.py
2 | #
3 | # Copyright (c) 2023, TheWisker
4 | # All rights reserved.
5 | #
6 | # This source code is licensed under the BSD-style license found in the
7 | # LICENSE file in the root directory of this source tree.
8 |
9 | import json
10 |
11 | pkgdatadir= '@pkgdatadir@'
12 |
13 | def get_translation_credits():
14 | translation_credits=''
15 | try:
16 | with open(f'{pkgdatadir}/CREDITS.json', 'r') as f:
17 | credits_json = json.load(f)
18 | for translator in sorted(credits_json.keys()):
19 | data = credits_json[translator]
20 | translation_credits += f'{translator} ('
21 | if type(data['lang']) == list:
22 | data['lang'].sort()
23 | translation_credits += ', '.join(data['lang'])
24 | else:
25 | translation_credits += data['lang']
26 | translation_credits += ')'
27 | if 'url' in data.keys():
28 | translation_credits += f" {data['url']}"
29 | elif 'email' in data.keys():
30 | translation_credits += f" <{data['email']}>"
31 | translation_credits += '\n'
32 | translation_credits = translation_credits.rstrip('\n')
33 | return translation_credits
34 | except Exception as e:
35 | print('[Error] Something went wrong when opening translator credits.')
36 | print(e)
37 | return ''
38 |
--------------------------------------------------------------------------------
/src/window.py:
--------------------------------------------------------------------------------
1 | # window.py
2 | #
3 | # Copyright (c) 2023, TheWisker
4 | # All rights reserved.
5 | #
6 | # This source code is licensed under the BSD-style license found in the
7 | # LICENSE file in the root directory of this source tree.
8 |
9 | from gi.repository import Adw, Gtk, Gio, GObject
10 |
11 | from cavasik.settings import CavasikSettings
12 | from cavasik.drawing_area import CavasikDrawingArea
13 | from cavasik.shortcuts import add_shortcuts
14 |
15 |
16 | class CavasikWindow(Adw.ApplicationWindow):
17 | __gtype_name__ = 'CavasikWindow'
18 |
19 | def __init__(self, **kwargs):
20 | super().__init__(**kwargs)
21 |
22 | self.settings = CavasikSettings.new(self.on_settings_changed)
23 | self.cava_sample = []
24 |
25 | self.build_ui()
26 | add_shortcuts(self, self.settings)
27 | self.connect('close-request', self.on_close_request)
28 | self.connect('notify::is-active', self.on_active_state_changed)
29 |
30 | def build_ui(self):
31 | self.set_title('Cavasik')
32 | self.set_size_request(170, 170)
33 | (width, height) = self.settings['size']
34 | self.set_default_size(width, height)
35 | if self.settings['maximized']:
36 | self.maximize()
37 | self.set_name('cavasik-window')
38 | self.toggle_sharp_corners()
39 | self.set_style()
40 | self.css_provider = Gtk.CssProvider.new()
41 | self.apply_colors()
42 |
43 | self.overlay = Gtk.Overlay.new()
44 | self.set_content(self.overlay)
45 |
46 | self.main_box = Gtk.Box.new(Gtk.Orientation.VERTICAL, 0)
47 | self.main_box.set_hexpand(True)
48 | self.main_box.set_vexpand(True)
49 | self.overlay.add_overlay(self.main_box)
50 |
51 | self.header = Adw.HeaderBar.new()
52 | self.header.add_css_class('flat')
53 | self.header.set_show_start_title_buttons( \
54 | self.settings['window-controls'])
55 | self.header.set_show_end_title_buttons(self.settings['window-controls'])
56 | self.header.set_title_widget(Gtk.Label.new(''))
57 | self.main_box.append(self.header)
58 |
59 | self.handle = Gtk.WindowHandle.new()
60 | self.handle.set_hexpand(True)
61 | self.handle.set_vexpand(True)
62 | self.main_box.append(self.handle)
63 |
64 | self.bin_spinner = Adw.Bin.new()
65 | self.bin_spinner.set_hexpand(True)
66 | self.bin_spinner.set_vexpand(True)
67 | self.handle.set_child(self.bin_spinner)
68 |
69 | self.spinner = Gtk.Spinner.new()
70 | self.spinner.set_spinning(True)
71 | self.spinner.set_size_request(50, -1)
72 | self.spinner.set_halign(Gtk.Align.CENTER)
73 | self.spinner.set_margin_bottom(40) # headerbar height
74 | self.bin_spinner.set_child(self.spinner)
75 |
76 | self.drawing_area = CavasikDrawingArea.new()
77 | self.drawing_area.spinner = self.spinner
78 | self.drawing_area.run()
79 | self.overlay.set_child(self.drawing_area)
80 |
81 | self.menu_button = Gtk.MenuButton.new()
82 | self.menu_button.set_valign(Gtk.Align.START)
83 | self.menu_button.set_icon_name('open-menu-symbolic')
84 | self.header.pack_start(self.menu_button)
85 |
86 | self.menu = Gio.Menu.new()
87 | self.menu.append(_('Preferences'), 'app.preferences')
88 | self.menu.append(_('Keyboard Shortcuts'), 'app.shortcuts')
89 | self.menu.append(_('About'), 'app.about')
90 | self.menu.append(_('Quit'), 'app.quit')
91 | self.menu_button.set_menu_model(self.menu)
92 |
93 | def set_style(self):
94 | if self.settings['widgets-style'] == 'light':
95 | Adw.StyleManager.get_default().set_color_scheme(Adw.ColorScheme.FORCE_LIGHT)
96 | else:
97 | Adw.StyleManager.get_default().set_color_scheme(Adw.ColorScheme.FORCE_DARK)
98 |
99 | def toggle_sharp_corners(self):
100 | if self.settings['sharp-corners']:
101 | self.add_css_class('sharp-corners')
102 | else:
103 | self.remove_css_class('sharp-corners')
104 |
105 | def apply_colors(self):
106 | try:
107 | color_profile = self.settings['color-profiles'][ \
108 | self.settings['active-color-profile']]
109 | colors = color_profile[2]
110 | except:
111 | colors = []
112 | if len(colors) == 0:
113 | self.get_style_context().remove_provider(self.css_provider)
114 | elif len(colors) == 1:
115 | self.css_data = '''#cavasik-window {
116 | background-color: rgba(%d, %d, %d, %f);
117 | }''' % colors[0]
118 | self.css_provider.load_from_data(self.css_data, -1)
119 | self.get_style_context().add_provider(self.css_provider, \
120 | Gtk.STYLE_PROVIDER_PRIORITY_USER)
121 | elif len(colors) > 1:
122 | self.css_data = '''#cavasik-window {
123 | background: linear-gradient(to bottom, '''
124 | for c in colors:
125 | self.css_data += 'rgba(%d, %d, %d, %f), ' % c
126 | self.css_data = self.css_data[:-2]
127 | self.css_data += ');}'
128 | self.css_provider.load_from_data(self.css_data, -1)
129 | self.get_style_context().add_provider(self.css_provider, \
130 | Gtk.STYLE_PROVIDER_PRIORITY_USER)
131 |
132 | def on_settings_changed(self):
133 | if self.settings['borderless-window']:
134 | self.add_css_class('borderless-window')
135 | elif self.has_css_class('borderless-window'):
136 | self.remove_css_class('borderless-window')
137 | self.header.set_show_start_title_buttons( \
138 | self.settings['window-controls'])
139 | self.header.set_show_end_title_buttons(self.settings['window-controls'])
140 | self.toggle_sharp_corners()
141 | self.set_style()
142 | self.apply_colors()
143 | try:
144 | self.on_active_state_changed()
145 | except:
146 | pass
147 |
148 | def on_close_request(self, obj):
149 | (width, height) = self.get_default_size()
150 | self.settings['size'] = (width, height)
151 | self.settings['maximized'] = self.is_maximized()
152 | if hasattr(self.get_application(), 'pref_win'):
153 | self.get_application().pref_win.close()
154 |
155 | def hide_header(self):
156 | if not self.is_active():
157 | self.header.set_show_start_title_buttons(False)
158 | self.header.set_show_end_title_buttons(False)
159 | self.menu_button.set_visible(False)
160 | return False # we don't need to restart the function
161 |
162 | def on_active_state_changed(self, *args):
163 | if self.settings['autohide-header'] and not self.is_active():
164 | # The window becomes inactive for a moment when
165 | # the menu button is pressed, making it impossible
166 | # to open the menu, so the delay is required
167 | GObject.timeout_add(100, self.hide_header)
168 | else:
169 | self.header.set_show_start_title_buttons( \
170 | self.settings['window-controls'])
171 | self.header.set_show_end_title_buttons( \
172 | self.settings['window-controls'])
173 | self.menu_button.set_visible(True)
174 |
175 |
--------------------------------------------------------------------------------
/utils/flatpak-pip-generator.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # Utility for generating flatpak module entries from a pip package
3 | # External to the Cavasik project
4 |
5 | __license__ = 'MIT'
6 |
7 | import argparse
8 | import json
9 | import hashlib
10 | import os
11 | import shutil
12 | import subprocess
13 | import sys
14 | import tempfile
15 | import urllib.request
16 |
17 | from collections import OrderedDict
18 | from typing import Dict
19 |
20 | try:
21 | import requirements
22 | except ImportError:
23 | exit('Requirements modules is not installed. Run "pip install requirements-parser"')
24 |
25 | parser = argparse.ArgumentParser()
26 | parser.add_argument('packages', nargs='*')
27 | parser.add_argument('--python2', action='store_true',
28 | help='Look for a Python 2 package')
29 | parser.add_argument('--cleanup', choices=['scripts', 'all'],
30 | help='Select what to clean up after build')
31 | parser.add_argument('--requirements-file', '-r',
32 | help='Specify requirements.txt file')
33 | parser.add_argument('--build-only', action='store_const',
34 | dest='cleanup', const='all',
35 | help='Clean up all files after build')
36 | parser.add_argument('--build-isolation', action='store_true',
37 | default=False,
38 | help=(
39 | 'Do not disable build isolation. '
40 | 'Mostly useful on pip that does\'t '
41 | 'support the feature.'
42 | ))
43 | parser.add_argument('--ignore-installed',
44 | type=lambda s: s.split(','),
45 | default='',
46 | help='Comma-separated list of package names for which pip '
47 | 'should ignore already installed packages. Useful when '
48 | 'the package is installed in the SDK but not in the '
49 | 'runtime.')
50 | parser.add_argument('--checker-data', action='store_true',
51 | help='Include x-checker-data in output for the "Flatpak External Data Checker"')
52 | parser.add_argument('--output', '-o',
53 | help='Specify output file name')
54 | parser.add_argument('--runtime',
55 | help='Specify a flatpak to run pip inside of a sandbox, ensures python version compatibility')
56 | parser.add_argument('--yaml', action='store_true',
57 | help='Use YAML as output format instead of JSON')
58 | parser.add_argument('--ignore-errors', action='store_true',
59 | help='Ignore errors when downloading packages')
60 | opts = parser.parse_args()
61 |
62 | if opts.yaml:
63 | try:
64 | import yaml
65 | except ImportError:
66 | exit('PyYAML modules is not installed. Run "pip install PyYAML"')
67 |
68 |
69 | def get_pypi_url(name: str, filename: str) -> str:
70 | url = 'https://pypi.org/pypi/{}/json'.format(name)
71 | print('Extracting download url for', name)
72 | with urllib.request.urlopen(url) as response:
73 | body = json.loads(response.read().decode('utf-8'))
74 | for release in body['releases'].values():
75 | for source in release:
76 | if source['filename'] == filename:
77 | return source['url']
78 | raise Exception('Failed to extract url from {}'.format(url))
79 |
80 |
81 | def get_tar_package_url_pypi(name: str, version: str) -> str:
82 | url = 'https://pypi.org/pypi/{}/{}/json'.format(name, version)
83 | with urllib.request.urlopen(url) as response:
84 | body = json.loads(response.read().decode('utf-8'))
85 | for ext in ['bz2', 'gz', 'xz', 'zip']:
86 | for source in body['urls']:
87 | if source['url'].endswith(ext):
88 | return source['url']
89 | err = 'Failed to get {}-{} source from {}'.format(name, version, url)
90 | raise Exception(err)
91 |
92 |
93 | def get_package_name(filename: str) -> str:
94 | if filename.endswith(('bz2', 'gz', 'xz', 'zip')):
95 | segments = filename.split('-')
96 | if len(segments) == 2:
97 | return segments[0]
98 | return '-'.join(segments[:len(segments) - 1])
99 | elif filename.endswith('whl'):
100 | segments = filename.split('-')
101 | if len(segments) == 5:
102 | return segments[0]
103 | candidate = segments[:len(segments) - 4]
104 | # Some packages list the version number twice
105 | # e.g. PyQt5-5.15.0-5.15.0-cp35.cp36.cp37.cp38-abi3-manylinux2014_x86_64.whl
106 | if candidate[-1] == segments[len(segments) - 4]:
107 | return '-'.join(candidate[:-1])
108 | return '-'.join(candidate)
109 | else:
110 | raise Exception(
111 | 'Downloaded filename: {} does not end with bz2, gz, xz, zip, or whl'.format(filename)
112 | )
113 |
114 |
115 | def get_file_version(filename: str) -> str:
116 | name = get_package_name(filename)
117 | segments = filename.split(name + '-')
118 | version = segments[1].split('-')[0]
119 | for ext in ['tar.gz', 'whl', 'tar.xz', 'tar.gz', 'tar.bz2', 'zip']:
120 | version = version.replace('.' + ext, '')
121 | return version
122 |
123 |
124 | def get_file_hash(filename: str) -> str:
125 | sha = hashlib.sha256()
126 | print('Generating hash for', filename.split('/')[-1])
127 | with open(filename, 'rb') as f:
128 | while True:
129 | data = f.read(1024 * 1024 * 32)
130 | if not data:
131 | break
132 | sha.update(data)
133 | return sha.hexdigest()
134 |
135 |
136 | def download_tar_pypi(url: str, tempdir: str) -> None:
137 | with urllib.request.urlopen(url) as response:
138 | file_path = os.path.join(tempdir, url.split('/')[-1])
139 | with open(file_path, 'x+b') as tar_file:
140 | shutil.copyfileobj(response, tar_file)
141 |
142 |
143 | def parse_continuation_lines(fin):
144 | for line in fin:
145 | line = line.rstrip('\n')
146 | while line.endswith('\\'):
147 | try:
148 | line = line[:-1] + next(fin).rstrip('\n')
149 | except StopIteration:
150 | exit('Requirements have a wrong number of line continuation characters "\\"')
151 | yield line
152 |
153 |
154 | def fprint(string: str) -> None:
155 | separator = '=' * 72 # Same as `flatpak-builder`
156 | print(separator)
157 | print(string)
158 | print(separator)
159 |
160 |
161 | packages = []
162 | if opts.requirements_file:
163 | requirements_file = os.path.expanduser(opts.requirements_file)
164 | try:
165 | with open(requirements_file, 'r') as req_file:
166 | reqs = parse_continuation_lines(req_file)
167 | reqs_as_str = '\n'.join([r.split('--hash')[0] for r in reqs])
168 | packages = list(requirements.parse(reqs_as_str))
169 | except FileNotFoundError:
170 | pass
171 |
172 | elif opts.packages:
173 | packages = list(requirements.parse('\n'.join(opts.packages)))
174 | with tempfile.NamedTemporaryFile('w', delete=False, prefix='requirements.') as req_file:
175 | req_file.write('\n'.join(opts.packages))
176 | requirements_file = req_file.name
177 | else:
178 | exit('Please specify either packages or requirements file argument')
179 |
180 | for i in packages:
181 | if i["name"].lower().startswith("pyqt"):
182 | print("PyQt packages are not supported by flapak-pip-generator")
183 | print("However, there is a BaseApp for PyQt available, that you should use")
184 | print("Visit https://github.com/flathub/com.riverbankcomputing.PyQt.BaseApp for more information")
185 | sys.exit(0)
186 |
187 | with open(requirements_file, 'r') as req_file:
188 | use_hash = '--hash=' in req_file.read()
189 |
190 | python_version = '2' if opts.python2 else '3'
191 | if opts.python2:
192 | pip_executable = 'pip2'
193 | else:
194 | pip_executable = 'pip3'
195 |
196 | if opts.runtime:
197 | flatpak_cmd = [
198 | 'flatpak',
199 | '--devel',
200 | '--share=network',
201 | '--filesystem=/tmp',
202 | '--command={}'.format(pip_executable),
203 | 'run',
204 | opts.runtime
205 | ]
206 | if opts.requirements_file:
207 | requirements_file = os.path.expanduser(opts.requirements_file)
208 | if os.path.exists(requirements_file):
209 | prefix = os.path.realpath(requirements_file)
210 | flag = '--filesystem={}'.format(prefix)
211 | flatpak_cmd.insert(1,flag)
212 | else:
213 | flatpak_cmd = [pip_executable]
214 |
215 | if opts.output:
216 | output_package = opts.output
217 | elif opts.requirements_file:
218 | output_package = 'python{}-{}'.format(
219 | python_version,
220 | os.path.basename(opts.requirements_file).replace('.txt', ''),
221 | )
222 | elif len(packages) == 1:
223 | output_package = 'python{}-{}'.format(
224 | python_version, packages[0].name,
225 | )
226 | else:
227 | output_package = 'python{}-modules'.format(python_version)
228 | if opts.yaml:
229 | output_filename = output_package + '.yaml'
230 | else:
231 | output_filename = output_package + '.json'
232 |
233 | modules = []
234 | vcs_modules = []
235 | sources = {}
236 |
237 | tempdir_prefix = 'pip-generator-{}'.format(os.path.basename(output_package))
238 | with tempfile.TemporaryDirectory(prefix=tempdir_prefix) as tempdir:
239 | pip_download = flatpak_cmd + [
240 | 'download',
241 | '--exists-action=i',
242 | '--dest',
243 | tempdir,
244 | '-r',
245 | requirements_file
246 | ]
247 | if use_hash:
248 | pip_download.append('--require-hashes')
249 |
250 | fprint('Downloading sources')
251 | cmd = ' '.join(pip_download)
252 | print('Running: "{}"'.format(cmd))
253 | try:
254 | subprocess.run(pip_download, check=True)
255 | except subprocess.CalledProcessError:
256 | print('Failed to download')
257 | print('Please fix the module manually in the generated file')
258 | if not opts.ignore_errors:
259 | print('Ignore the error by passing --ignore-errors')
260 | raise
261 |
262 | if not opts.requirements_file:
263 | try:
264 | os.remove(requirements_file)
265 | except FileNotFoundError:
266 | pass
267 |
268 | fprint('Downloading arch independent packages')
269 | for filename in os.listdir(tempdir):
270 | if not filename.endswith(('bz2', 'any.whl', 'gz', 'xz', 'zip')):
271 | version = get_file_version(filename)
272 | name = get_package_name(filename)
273 | url = get_tar_package_url_pypi(name, version)
274 | print('Deleting', filename)
275 | try:
276 | os.remove(os.path.join(tempdir, filename))
277 | except FileNotFoundError:
278 | pass
279 | print('Downloading {}'.format(url))
280 | download_tar_pypi(url, tempdir)
281 |
282 | files = {get_package_name(f): [] for f in os.listdir(tempdir)}
283 |
284 | for filename in os.listdir(tempdir):
285 | name = get_package_name(filename)
286 | files[name].append(filename)
287 |
288 | # Delete redundant sources, for vcs sources
289 | for name in files:
290 | if len(files[name]) > 1:
291 | zip_source = False
292 | for f in files[name]:
293 | if f.endswith('.zip'):
294 | zip_source = True
295 | if zip_source:
296 | for f in files[name]:
297 | if not f.endswith('.zip'):
298 | try:
299 | os.remove(os.path.join(tempdir, f))
300 | except FileNotFoundError:
301 | pass
302 |
303 | vcs_packages = {
304 | x.name: {'vcs': x.vcs, 'revision': x.revision, 'uri': x.uri}
305 | for x in packages
306 | if x.vcs
307 | }
308 |
309 | fprint('Obtaining hashes and urls')
310 | for filename in os.listdir(tempdir):
311 | name = get_package_name(filename)
312 | sha256 = get_file_hash(os.path.join(tempdir, filename))
313 |
314 | if name in vcs_packages:
315 | uri = vcs_packages[name]['uri']
316 | revision = vcs_packages[name]['revision']
317 | vcs = vcs_packages[name]['vcs']
318 | url = 'https://' + uri.split('://', 1)[1]
319 | s = 'commit'
320 | if vcs == 'svn':
321 | s = 'revision'
322 | source = OrderedDict([
323 | ('type', vcs),
324 | ('url', url),
325 | (s, revision),
326 | ])
327 | is_vcs = True
328 | else:
329 | url = get_pypi_url(name, filename)
330 | source = OrderedDict([
331 | ('type', 'file'),
332 | ('url', url),
333 | ('sha256', sha256)])
334 | if opts.checker_data:
335 | source['x-checker-data'] = {
336 | 'type': 'pypi',
337 | 'name': name}
338 | if url.endswith(".whl"):
339 | source['x-checker-data']['packagetype'] = 'bdist_wheel'
340 | is_vcs = False
341 | sources[name] = {'source': source, 'vcs': is_vcs}
342 |
343 | # Python3 packages that come as part of org.freedesktop.Sdk.
344 | system_packages = ['cython', 'easy_install', 'mako', 'markdown', 'meson', 'pip', 'pygments', 'setuptools', 'six', 'wheel']
345 |
346 | fprint('Generating dependencies')
347 | for package in packages:
348 |
349 | if package.name is None:
350 | print('Warning: skipping invalid requirement specification {} because it is missing a name'.format(package.line), file=sys.stderr)
351 | print('Append #egg= to the end of the requirement line to fix', file=sys.stderr)
352 | continue
353 | elif package.name.casefold() in system_packages:
354 | print(f"{package.name} is in system_packages. Skipping.")
355 | continue
356 |
357 | if len(package.extras) > 0:
358 | extras = '[' + ','.join(extra for extra in package.extras) + ']'
359 | else:
360 | extras = ''
361 |
362 | version_list = [x[0] + x[1] for x in package.specs]
363 | version = ','.join(version_list)
364 |
365 | if package.vcs:
366 | revision = ''
367 | if package.revision:
368 | revision = '@' + package.revision
369 | pkg = package.uri + revision + '#egg=' + package.name
370 | else:
371 | pkg = package.name + extras + version
372 |
373 | dependencies = []
374 | # Downloads the package again to list dependencies
375 |
376 | tempdir_prefix = 'pip-generator-{}'.format(package.name)
377 | with tempfile.TemporaryDirectory(prefix='{}-{}'.format(tempdir_prefix, package.name)) as tempdir:
378 | pip_download = flatpak_cmd + [
379 | 'download',
380 | '--exists-action=i',
381 | '--dest',
382 | tempdir,
383 | ]
384 | try:
385 | print('Generating dependencies for {}'.format(package.name))
386 | subprocess.run(pip_download + [pkg], check=True, stdout=subprocess.DEVNULL)
387 | for filename in sorted(os.listdir(tempdir)):
388 | dep_name = get_package_name(filename)
389 | if dep_name.casefold() in system_packages:
390 | continue
391 | dependencies.append(dep_name)
392 |
393 | except subprocess.CalledProcessError:
394 | print('Failed to download {}'.format(package.name))
395 |
396 | is_vcs = True if package.vcs else False
397 | package_sources = []
398 | for dependency in dependencies:
399 | if dependency in sources:
400 | source = sources[dependency]
401 | elif dependency.replace('_', '-') in sources:
402 | source = sources[dependency.replace('_', '-')]
403 | else:
404 | continue
405 |
406 | if not (not source['vcs'] or is_vcs):
407 | continue
408 |
409 | package_sources.append(source['source'])
410 |
411 | if package.vcs:
412 | name_for_pip = '.'
413 | else:
414 | name_for_pip = pkg
415 |
416 | module_name = 'python{}-{}'.format(python_version, package.name)
417 |
418 | pip_command = [
419 | pip_executable,
420 | 'install',
421 | '--verbose',
422 | '--exists-action=i',
423 | '--no-index',
424 | '--find-links="file://${PWD}"',
425 | '--prefix=${FLATPAK_DEST}',
426 | '"{}"'.format(name_for_pip)
427 | ]
428 | if package.name in opts.ignore_installed:
429 | pip_command.append('--ignore-installed')
430 | if not opts.build_isolation:
431 | pip_command.append('--no-build-isolation')
432 |
433 | module = OrderedDict([
434 | ('name', module_name),
435 | ('buildsystem', 'simple'),
436 | ('build-commands', [' '.join(pip_command)]),
437 | ('sources', package_sources),
438 | ])
439 | if opts.cleanup == 'all':
440 | module['cleanup'] = ['*']
441 | elif opts.cleanup == 'scripts':
442 | module['cleanup'] = ['/bin', '/share/man/man1']
443 |
444 | if package.vcs:
445 | vcs_modules.append(module)
446 | else:
447 | modules.append(module)
448 |
449 | modules = vcs_modules + modules
450 | if len(modules) == 1:
451 | pypi_module = modules[0]
452 | else:
453 | pypi_module = {
454 | 'name': output_package,
455 | 'buildsystem': 'simple',
456 | 'build-commands': [],
457 | 'modules': modules,
458 | }
459 |
460 | print()
461 | with open(output_filename, 'w') as output:
462 | if opts.yaml:
463 | class OrderedDumper(yaml.Dumper):
464 | def increase_indent(self, flow=False, indentless=False):
465 | return super(OrderedDumper, self).increase_indent(flow, False)
466 |
467 | def dict_representer(dumper, data):
468 | return dumper.represent_dict(data.items())
469 |
470 | OrderedDumper.add_representer(OrderedDict, dict_representer)
471 |
472 | output.write("# Generated with flatpak-pip-generator " + " ".join(sys.argv[1:]) + "\n")
473 | yaml.dump(pypi_module, output, Dumper=OrderedDumper)
474 | else:
475 | output.write(json.dumps(pypi_module, indent=4))
476 | print('Output saved to {}'.format(output_filename))
477 |
--------------------------------------------------------------------------------