├── .editorconfig ├── .eslintrc.yml ├── .gitignore ├── .gitlab-ci.yml ├── .gitlab-ci ├── commit-rules.yml └── install-meson-project.sh ├── COPYING ├── LICENSES ├── CC-BY-SA-4.0.txt ├── CC0-1.0.txt ├── GPL-2.0-or-later.txt ├── LGPL-2.0-or-later.txt └── MIT.txt ├── NEWS ├── README.md ├── REUSE.toml ├── data ├── Polari.client ├── icons │ ├── REUSE.toml │ ├── hicolor │ │ ├── scalable │ │ │ └── apps │ │ │ │ ├── org.gnome.Polari.Devel.svg │ │ │ │ └── org.gnome.Polari.svg │ │ └── symbolic │ │ │ └── apps │ │ │ └── org.gnome.Polari-symbolic.svg │ ├── meson.build │ ├── org.gnome.Polari.Source.svg │ └── org.gnome.Polari.Source.svg.license ├── meson.build ├── metainfo │ ├── meson.build │ ├── org.gnome.Polari.metainfo.xml.in │ ├── polari-connection-properties.png │ ├── polari-connection.png │ ├── polari-join-dialog.png │ ├── polari-main.png │ └── polari-user-list.png ├── org.freedesktop.Telepathy.Client.Polari.service.in ├── org.gnome.Polari.data.gresource.xml ├── org.gnome.Polari.desktop.in ├── org.gnome.Polari.gschema.xml ├── org.gnome.Polari.lib.gresource.xml ├── org.gnome.Polari.service.in └── resources │ ├── connection-details.ui │ ├── connection-properties.ui │ ├── entry-area.ui │ ├── help-overlay.ui │ ├── initial-setup-window.ui │ ├── join-room-dialog.ui │ ├── main-window.ui │ ├── networks.json │ ├── nick-popover.ui │ ├── people-symbolic.svg │ ├── people-symbolic.svg.license │ ├── polari-user-notify-symbolic.svg │ ├── polari-user-notify-symbolic.svg.license │ ├── polari.ontology │ ├── room-list-header.ui │ ├── room-list-row.ui │ ├── server-room-list.ui │ ├── sparql │ └── get-room-events.rq │ ├── style.css │ ├── user-details.ui │ └── user-popover.ui ├── flatpak ├── idle-build-fix.patch ├── org.gnome.Polari.json ├── tp-change-uniquify.patch └── tp-glib-casts.patch ├── help ├── C │ ├── images │ │ ├── Polari.png │ │ ├── polari-paste.png │ │ └── polari-screenshot.png │ ├── index.page │ ├── introduction.page │ ├── irc-commands.page │ ├── irc-join-room.page │ ├── irc-nick-password.page │ ├── irc-start-conversation.page │ ├── legal.xml │ ├── network-connect.page │ ├── overview.page │ └── sharing.page ├── LINGUAS ├── ca │ └── ca.po ├── cs │ └── cs.po ├── da │ ├── da.po │ └── images │ │ ├── polari-paste.png │ │ └── polari-screenshot.png ├── de │ ├── de.po │ └── images │ │ ├── polari-paste.png │ │ └── polari-screenshot.png ├── el │ └── el.po ├── es │ └── es.po ├── eu │ └── eu.po ├── fr │ └── fr.po ├── hu │ └── hu.po ├── id │ └── id.po ├── meson.build ├── nl │ └── nl.po ├── pl │ ├── images │ │ ├── polari-paste.png │ │ └── polari-screenshot.png │ └── pl.po ├── pt │ └── pt.po ├── pt_BR │ └── pt_BR.po ├── ru │ └── ru.po ├── sv │ └── sv.po ├── tr │ └── tr.po └── uk │ └── uk.po ├── lint ├── eslintrc-gjs.yml └── eslintrc-polari.yml ├── logo.png ├── logo.png.license ├── meson.build ├── meson.format ├── meson.options ├── meson └── check-version.py ├── po ├── LINGUAS ├── POTFILES.in ├── af.po ├── ar.po ├── as.po ├── be.po ├── bg.po ├── bs.po ├── ca.po ├── ca@valencia.po ├── ckb.po ├── cs.po ├── da.po ├── de.po ├── el.po ├── en_GB.po ├── eo.po ├── es.po ├── et.po ├── eu.po ├── fa.po ├── fi.po ├── fr.po ├── fur.po ├── gl.po ├── he.po ├── hi.po ├── hr.po ├── hu.po ├── id.po ├── is.po ├── it.po ├── ja.po ├── ka.po ├── kk.po ├── ko.po ├── lo.po ├── lt.po ├── lv.po ├── meson.build ├── ml.po ├── ms.po ├── nb.po ├── ne.po ├── nl.po ├── oc.po ├── pa.po ├── pl.po ├── pt.po ├── pt_BR.po ├── ro.po ├── ru.po ├── sk.po ├── sl.po ├── sr.po ├── sr@latin.po ├── sv.po ├── te.po ├── tg.po ├── th.po ├── tr.po ├── uk.po ├── vi.po ├── zh_CN.po ├── zh_HK.po └── zh_TW.po ├── polari.doap └── src ├── accountsMonitor.js ├── application.js ├── chatView.js ├── config.js.in ├── connections.js ├── entryArea.js ├── initialSetup.js ├── ircParser.js ├── joinDialog.js ├── lib ├── polari-client-factory.c ├── polari-client-factory.h ├── polari-message-private.h ├── polari-message.c ├── polari-message.h ├── polari-room.c ├── polari-room.h ├── polari-tp-autocleanup.h ├── polari-tpl-importer.c ├── polari-tpl-importer.h ├── polari-util.c └── polari-util.h ├── logger.js ├── main.js ├── mainWindow.js ├── meson.build ├── networksManager.js ├── org.gnome.Polari.src.gresource.xml ├── pasteManager.js ├── polari.c ├── roomList.js ├── roomManager.js ├── roomStack.js ├── serverRoomManager.js ├── tabCompletion.js ├── telepathyClient.js ├── tests ├── meson.build └── test-util.c ├── thumbnailer.js ├── urlPreview.js ├── userList.js ├── userTracker.js └── utils.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2024 Florian Müllner 2 | # SPDX-License-Identifier: CC0-1.0 3 | 4 | root = true 5 | 6 | [*] 7 | charset = utf-8 8 | end_of_line = lf 9 | 10 | indent_style = space 11 | trim_trailing_whitespace = true 12 | 13 | [*.js] 14 | indent_size = 4 15 | 16 | [*.[ch]] 17 | indent_size = 2 18 | 19 | [*.gresource.xml] 20 | indent_size = 2 21 | 22 | [*.ui] 23 | indent_size = 2 24 | 25 | [meson.build] 26 | indent_size = 2 27 | -------------------------------------------------------------------------------- /.eslintrc.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2018 Florian Müllner 2 | # 3 | # SPDX-License-Identifier: MIT OR LGPL-2.0-or-later 4 | extends: 5 | - ./lint/eslintrc-gjs.yml 6 | - ./lint/eslintrc-polari.yml 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2013 Florian Müllner 2 | # 3 | # SPDX-License-Identifier: CC0-1.0 4 | *.pot 5 | *.sw? 6 | *.tar.xz 7 | .buildconfig 8 | .flatpak-builder 9 | *~ 10 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2017 Florian Müllner 2 | # 3 | # SPDX-License-Identifier: GPL-2.0-or-later 4 | 5 | include: 6 | - project: 'GNOME/citemplates' 7 | files: 'templates/default-rules.yml' 8 | - project: 'GNOME/citemplates' 9 | file: 'flatpak/flatpak_ci_initiative.yml' 10 | - component: gitlab.gnome.org/GNOME/citemplates/release-service@master 11 | inputs: 12 | dist-job-name: "flatpak" 13 | tarball-artifact-path: "${TARBALL_ARTIFACT_PATH}" 14 | - project: 'Infrastructure/freedesktop-ci-templates' 15 | file: 'templates/fedora.yml' 16 | ref: '057b052e682d8e5a20c1eb2dd60d5b87d2b56856' 17 | - project: 'Infrastructure/freedesktop-ci-templates' 18 | file: 'templates/ci-fairy.yml' 19 | ref: '34f4ade99434043f88e164933f570301fd18b125' 20 | 21 | stages: 22 | - pre_review 23 | - prepare 24 | - review 25 | - build 26 | - deploy 27 | - housekeeping 28 | 29 | variables: 30 | FDO_UPSTREAM_REPO: GNOME/polari 31 | FLATPAK_MODULE: "polari" 32 | MANIFEST_PATH: "flatpak/org.gnome.Polari.json" 33 | TARBALL_ARTIFACT_PATH: ".flatpak-builder/build/${FLATPAK_MODULE}/_flatpak_build/meson-dist/${CI_PROJECT_NAME}-${CI_COMMIT_TAG}.tar.xz" 34 | BUNDLE: "polari-git.flatpak" 35 | 36 | .pipeline_guard: &pipeline_guard 37 | rules: 38 | - if: '$CI_PIPELINE_SOURCE == "merge_request_event"' 39 | - if: '$CI_PIPELINE_SOURCE == "schedule"' 40 | - if: '$CI_COMMIT_TAG' 41 | - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH' 42 | - if: '$CI_COMMIT_BRANCH =~ /^gnome-[0-9-]+$/' 43 | - when: 'manual' 44 | 45 | .polari.fedora: 46 | variables: 47 | FDO_DISTRIBUTION_VERSION: 41 48 | FDO_DISTRIBUTION_TAG: '2024-12-18.0' 49 | FDO_DISTRIBUTION_PACKAGES: > 50 | meson gjs git 51 | nodejs npm 52 | FDO_DISTRIBUTION_EXEC: | 53 | # For static analysis with eslint 54 | npm install -g eslint@^8.0.0 eslint-plugin-jsdoc@^46.0.0 && 55 | 56 | ./.gitlab-ci/install-meson-project.sh \ 57 | https://gitlab.gnome.org/World/javascript/gjs-ci-tools.git \ 58 | main 59 | 60 | .prereview_req: &prereview_req 61 | needs: 62 | - check_commit_log 63 | - check-merge-request 64 | 65 | check_commit_log: 66 | extends: 67 | - .fdo.ci-fairy 68 | stage: pre_review 69 | script: 70 | - if [[ x"$CI_MERGE_REQUEST_TARGET_BRANCH_NAME" != "x" ]] ; 71 | then 72 | ci-fairy check-commits --junit-xml=commit-message-junit-report.xml ; 73 | else 74 | echo "Not a merge request" ; 75 | fi 76 | <<: *pipeline_guard 77 | artifacts: 78 | expire_in: 1 week 79 | paths: 80 | - commit-message-junit-report.xml 81 | reports: 82 | junit: commit-message-junit-report.xml 83 | 84 | check-merge-request: 85 | extends: 86 | - .fdo.ci-fairy 87 | stage: pre_review 88 | script: 89 | - if [[ x"$CI_MERGE_REQUEST_TARGET_BRANCH_NAME" != "x" ]] ; 90 | then 91 | ci-fairy check-merge-request --require-allow-collaboration --junit-xml=check-merge-request-report.xml ; 92 | else 93 | echo "Not a merge request" ; 94 | fi 95 | <<: *pipeline_guard 96 | artifacts: 97 | expire_in: 1 week 98 | paths: 99 | - check-merge-request-report.xml 100 | reports: 101 | junit: check-merge-request-report.xml 102 | 103 | check-reuse: 104 | stage: pre_review 105 | image: 106 | name: fsfe/reuse:latest 107 | entrypoint: [""] 108 | script: 109 | - reuse lint 110 | 111 | build-fedora-container: 112 | extends: 113 | - .fdo.container-build@fedora@x86_64 114 | - .polari.fedora 115 | stage: prepare 116 | <<: *prereview_req 117 | 118 | eslint: 119 | extends: 120 | - .fdo.distribution-image@fedora 121 | - .polari.fedora 122 | stage: review 123 | variables: 124 | LINT_LOG: "eslint-report.xml" 125 | needs: 126 | - build-fedora-container 127 | script: 128 | - eslint -o "$LINT_LOG" -f junit --resolve-plugins-relative-to $(npm root -g) src 129 | artifacts: 130 | paths: 131 | - "$LINT_LOG" 132 | reports: 133 | junit: "$LINT_LOG" 134 | 135 | js-check: 136 | extends: 137 | - .fdo.distribution-image@fedora 138 | - .polari.fedora 139 | stage: review 140 | needs: 141 | - build-fedora-container 142 | script: 143 | - gjs-check-syntax 144 | 145 | .flatpak-template: 146 | stage: build 147 | <<: *prereview_req 148 | variables: 149 | RUNTIME_REPO: "https://nightly.gnome.org/gnome-nightly.flatpakrepo" 150 | APP_ID: "org.gnome.Polari" 151 | extends: .flatpak 152 | 153 | flatpak-snapshot: 154 | variables: 155 | BRANCH: "snapshot" 156 | CONFIG_OPTS: "-Dsnapshot=true --werror --warnlevel 2" 157 | extends: .flatpak-template 158 | rules: 159 | - if: '$CI_PIPELINE_SOURCE == "merge_request_event"' 160 | 161 | flatpak: 162 | extends: .flatpak-template 163 | rules: 164 | - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH' 165 | - if: '$CI_COMMIT_BRANCH =~ /^gnome-[0-9-]+$/' 166 | - if: '$CI_COMMIT_TAG' 167 | 168 | nightly: 169 | extends: '.publish_nightly' 170 | needs: 171 | - flatpak 172 | 173 | check-flatpak-deps: 174 | image: 175 | name: ghcr.io/flathub/flatpak-external-data-checker 176 | entrypoint: [""] 177 | stage: housekeeping 178 | <<: *prereview_req 179 | script: 180 | - "/app/flatpak-external-data-checker ${MANIFEST_PATH}" 181 | rules: 182 | - if: '$CI_PIPELINE_SOURCE == "schedule" && $POLARI_SCHEDULED_JOB == "x-checker"' 183 | -------------------------------------------------------------------------------- /.gitlab-ci/commit-rules.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2020 Florian Müllner 2 | # 3 | # SPDX-License-Identifier: GPL-2.0-or-later 4 | 5 | patterns: 6 | deny: 7 | - regex: '^$CI_MERGE_REQUEST_PROJECT_URL/(-/)?merge_requests/$CI_MERGE_REQUEST_IID$' 8 | message: Commit message must not contain a link to its own merge request 9 | - regex: '^[^:]+: [a-z]' 10 | message: "Commit message subject should be properly Capitalized. E.g. 'chatView: Marginalize extradicity'" 11 | where: subject 12 | - regex: '^\S*\.js:' 13 | message: Commit message subject prefix should not include .js 14 | where: subject 15 | -------------------------------------------------------------------------------- /.gitlab-ci/install-meson-project.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # SPDX-FileCopyrightText: 2021 Florian Müllner 4 | # 5 | # SPDX-License-Identifier: GPL-2.0-or-later 6 | 7 | set -e 8 | 9 | usage() { 10 | cat <<-EOF 11 | Usage: $(basename $0) [OPTION…] REPO_URL COMMIT 12 | 13 | Check out and install a meson project 14 | 15 | Options: 16 | -Dkey=val Option to pass on to meson 17 | --subdir=DIR Build subdirectory instead of whole project 18 | --prepare=SCRIPT Script to run before build 19 | --libdir=DIR Setup the project with a different libdir 20 | --destdir=DIR Install the project to DIR, can be used 21 | several times to install to multiple destdirs 22 | 23 | -h, --help Display this help 24 | 25 | EOF 26 | } 27 | 28 | TEMP=$(getopt \ 29 | --name=$(basename $0) \ 30 | --options='D:h' \ 31 | --longoptions='subdir:' \ 32 | --longoptions='prepare:' \ 33 | --longoptions='libdir:' \ 34 | --longoptions='destdir:' \ 35 | --longoptions='help' \ 36 | -- "$@") 37 | 38 | eval set -- "$TEMP" 39 | unset TEMP 40 | 41 | MESON_OPTIONS=() 42 | SUBDIR=. 43 | PREPARE=: 44 | DESTDIRS=() 45 | 46 | while true; do 47 | case "$1" in 48 | -D) 49 | MESON_OPTIONS+=( -D$2 ) 50 | shift 2 51 | ;; 52 | 53 | --subdir) 54 | SUBDIR=$2 55 | shift 2 56 | ;; 57 | 58 | --prepare) 59 | PREPARE=$2 60 | shift 2 61 | ;; 62 | 63 | --libdir) 64 | MESON_OPTIONS+=( --libdir=$2 ) 65 | shift 2 66 | ;; 67 | 68 | --destdir) 69 | DESTDIRS+=( $2 ) 70 | shift 2 71 | ;; 72 | 73 | -h|--help) 74 | usage 75 | exit 0 76 | ;; 77 | 78 | --) 79 | shift 80 | break 81 | ;; 82 | esac 83 | done 84 | 85 | if [[ $# -lt 2 ]]; then 86 | usage 87 | exit 1 88 | fi 89 | 90 | REPO_URL="$1" 91 | COMMIT="$2" 92 | 93 | [[ ${#DESTDIRS[@]} == 0 ]] && DESTDIRS+=( / ) 94 | 95 | CHECKOUT_DIR=$(mktemp --directory) 96 | trap "rm -rf $CHECKOUT_DIR" EXIT 97 | 98 | git clone --depth 1 "$REPO_URL" -b "$COMMIT" "$CHECKOUT_DIR" 99 | 100 | pushd "$CHECKOUT_DIR/$SUBDIR" 101 | sh -c "$PREPARE" 102 | meson setup --prefix=/usr _build "${MESON_OPTIONS[@]}" 103 | 104 | # Install it to all specified dest dirs 105 | for destdir in "${DESTDIRS[@]}"; do 106 | # don't use --destdir when installing to root, 107 | # so post-install hooks are run 108 | [[ $destdir == / ]] && destdir= 109 | sudo meson install -C _build ${destdir:+--destdir=$destdir} 110 | done 111 | popd 112 | -------------------------------------------------------------------------------- /LICENSES/CC0-1.0.txt: -------------------------------------------------------------------------------- 1 | Creative Commons Legal Code 2 | 3 | CC0 1.0 Universal CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES 4 | NOT PROVIDE LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE 5 | AN ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS INFORMATION 6 | ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES REGARDING THE USE 7 | OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED HEREUNDER, AND DISCLAIMS 8 | LIABILITY FOR DAMAGES RESULTING FROM THE USE OF THIS DOCUMENT OR THE INFORMATION 9 | OR WORKS PROVIDED HEREUNDER. 10 | 11 | Statement of Purpose 12 | 13 | The laws of most jurisdictions throughout the world automatically confer exclusive 14 | Copyright and Related Rights (defined below) upon the creator and subsequent 15 | owner(s) (each and all, an "owner") of an original work of authorship and/or 16 | a database (each, a "Work"). 17 | 18 | Certain owners wish to permanently relinquish those rights to a Work for the 19 | purpose of contributing to a commons of creative, cultural and scientific 20 | works ("Commons") that the public can reliably and without fear of later claims 21 | of infringement build upon, modify, incorporate in other works, reuse and 22 | redistribute as freely as possible in any form whatsoever and for any purposes, 23 | including without limitation commercial purposes. These owners may contribute 24 | to the Commons to promote the ideal of a free culture and the further production 25 | of creative, cultural and scientific works, or to gain reputation or greater 26 | distribution for their Work in part through the use and efforts of others. 27 | 28 | For these and/or other purposes and motivations, and without any expectation 29 | of additional consideration or compensation, the person associating CC0 with 30 | a Work (the "Affirmer"), to the extent that he or she is an owner of Copyright 31 | and Related Rights in the Work, voluntarily elects to apply CC0 to the Work 32 | and publicly distribute the Work under its terms, with knowledge of his or 33 | her Copyright and Related Rights in the Work and the meaning and intended 34 | legal effect of CC0 on those rights. 35 | 36 | 1. Copyright and Related Rights. A Work made available under CC0 may be protected 37 | by copyright and related or neighboring rights ("Copyright and Related Rights"). 38 | Copyright and Related Rights include, but are not limited to, the following: 39 | 40 | i. the right to reproduce, adapt, distribute, perform, display, communicate, 41 | and translate a Work; 42 | 43 | ii. moral rights retained by the original author(s) and/or performer(s); 44 | 45 | iii. publicity and privacy rights pertaining to a person's image or likeness 46 | depicted in a Work; 47 | 48 | iv. rights protecting against unfair competition in regards to a Work, subject 49 | to the limitations in paragraph 4(a), below; 50 | 51 | v. rights protecting the extraction, dissemination, use and reuse of data 52 | in a Work; 53 | 54 | vi. database rights (such as those arising under Directive 96/9/EC of the 55 | European Parliament and of the Council of 11 March 1996 on the legal protection 56 | of databases, and under any national implementation thereof, including any 57 | amended or successor version of such directive); and 58 | 59 | vii. other similar, equivalent or corresponding rights throughout the world 60 | based on applicable law or treaty, and any national implementations thereof. 61 | 62 | 2. Waiver. To the greatest extent permitted by, but not in contravention of, 63 | applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and 64 | unconditionally waives, abandons, and surrenders all of Affirmer's Copyright 65 | and Related Rights and associated claims and causes of action, whether now 66 | known or unknown (including existing as well as future claims and causes of 67 | action), in the Work (i) in all territories worldwide, (ii) for the maximum 68 | duration provided by applicable law or treaty (including future time extensions), 69 | (iii) in any current or future medium and for any number of copies, and (iv) 70 | for any purpose whatsoever, including without limitation commercial, advertising 71 | or promotional purposes (the "Waiver"). Affirmer makes the Waiver for the 72 | benefit of each member of the public at large and to the detriment of Affirmer's 73 | heirs and successors, fully intending that such Waiver shall not be subject 74 | to revocation, rescission, cancellation, termination, or any other legal or 75 | equitable action to disrupt the quiet enjoyment of the Work by the public 76 | as contemplated by Affirmer's express Statement of Purpose. 77 | 78 | 3. Public License Fallback. Should any part of the Waiver for any reason be 79 | judged legally invalid or ineffective under applicable law, then the Waiver 80 | shall be preserved to the maximum extent permitted taking into account Affirmer's 81 | express Statement of Purpose. In addition, to the extent the Waiver is so 82 | judged Affirmer hereby grants to each affected person a royalty-free, non 83 | transferable, non sublicensable, non exclusive, irrevocable and unconditional 84 | license to exercise Affirmer's Copyright and Related Rights in the Work (i) 85 | in all territories worldwide, (ii) for the maximum duration provided by applicable 86 | law or treaty (including future time extensions), (iii) in any current or 87 | future medium and for any number of copies, and (iv) for any purpose whatsoever, 88 | including without limitation commercial, advertising or promotional purposes 89 | (the "License"). The License shall be deemed effective as of the date CC0 90 | was applied by Affirmer to the Work. Should any part of the License for any 91 | reason be judged legally invalid or ineffective under applicable law, such 92 | partial invalidity or ineffectiveness shall not invalidate the remainder of 93 | the License, and in such case Affirmer hereby affirms that he or she will 94 | not (i) exercise any of his or her remaining Copyright and Related Rights 95 | in the Work or (ii) assert any associated claims and causes of action with 96 | respect to the Work, in either case contrary to Affirmer's express Statement 97 | of Purpose. 98 | 99 | 4. Limitations and Disclaimers. 100 | 101 | a. No trademark or patent rights held by Affirmer are waived, abandoned, surrendered, 102 | licensed or otherwise affected by this document. 103 | 104 | b. Affirmer offers the Work as-is and makes no representations or warranties 105 | of any kind concerning the Work, express, implied, statutory or otherwise, 106 | including without limitation warranties of title, merchantability, fitness 107 | for a particular purpose, non infringement, or the absence of latent or other 108 | defects, accuracy, or the present or absence of errors, whether or not discoverable, 109 | all to the greatest extent permissible under applicable law. 110 | 111 | c. Affirmer disclaims responsibility for clearing rights of other persons 112 | that may apply to the Work or any use thereof, including without limitation 113 | any person's Copyright and Related Rights in the Work. Further, Affirmer disclaims 114 | responsibility for obtaining any necessary consents, permissions or other 115 | rights required for any use of the Work. 116 | 117 | d. Affirmer understands and acknowledges that Creative Commons is not a party 118 | to this document and has no duty or obligation with respect to this CC0 or 119 | use of the Work. 120 | -------------------------------------------------------------------------------- /LICENSES/MIT.txt: -------------------------------------------------------------------------------- 1 | MIT License Copyright (c) 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice (including the next 11 | paragraph) shall be included in all copies or substantial portions of the 12 | Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 16 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS 17 | OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 18 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF 19 | OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 5 | 6 | # ![polari-logo] Polari 7 | 8 | Polari is a simple Internet Relay Chat (IRC) client that is designed to 9 | integrate seamlessly with the GNOME desktop. You can find additional 10 | information on its [GNOME app page][app-page]. 11 | 12 | All interations with the project should follow the [Code of Conduct][conduct]. 13 | 14 | ## Hacking 15 | 16 | The easiest way to build the latest development version of Polari and 17 | start hacking on the code is to follow the recommended [GNOME guide] 18 | [build-instructions]. 19 | 20 | ## Getting in Touch 21 | 22 | Surprise, there is an IRC channel! If you have any questions regarding the 23 | use or development of Polari, want to discuss design or simply hang out 24 | with nice folks, please join us in [#gnome-polari on irc.libera.chat][irc-channel]. 25 | 26 | ## How to report bugs 27 | 28 | If you found a problem or have a feature suggestion, please report the 29 | issue to the GNOME [bug tracking system][bug-tracker]. 30 | 31 | 32 | [app-page]: https://apps.gnome.org/Polari 33 | [conduct]: https://conduct.gnome.org 34 | [build-instructions]: https://welcome.gnome.org/app/Polari 35 | [irc-channel]: irc://irc.libera.chat/%23gnome-polari 36 | [bug-tracker]: https://gitlab.gnome.org/GNOME/polari/issues 37 | [polari-logo]: logo.png 38 | -------------------------------------------------------------------------------- /REUSE.toml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2024 Florian Müllner 2 | # SPDX-License-Identifier: CC0-1.0 3 | version = 1 4 | SPDX-PackageName = "polari" 5 | SPDX-PackageSupplier = "Florian Müllner " 6 | SPDX-PackageDownloadLocation = "https://gitlab.gnome.org/GNOME/polari" 7 | 8 | [[annotations]] 9 | path = "NEWS" 10 | SPDX-FileCopyrightText = "2013 Florian Müllner " 11 | SPDX-License-Identifier = "CC-BY-SA-4.0" 12 | 13 | [[annotations]] 14 | path = ["**.service.in", "**.client", "**.desktop.in", "data/resources/networks.json"] 15 | SPDX-FileCopyrightText = "Florian Müllner " 16 | SPDX-License-Identifier = "GPL-2.0-or-later" 17 | 18 | [[annotations]] 19 | # managed by translation teams 20 | path = ["po/**.po", "help/**/**.po"] 21 | SPDX-FileCopyrightText = "GNOME Translation Teams " 22 | SPDX-License-Identifier = "GPL-2.0-or-later" 23 | 24 | [[annotations]] 25 | # managed by translation teams 26 | path = ["po/LINGUAS", "help/LINGUAS", "po/POTFILES.in"] 27 | SPDX-FileCopyrightText = "No rights reserved" 28 | SPDX-License-Identifier = "CC0-1.0" 29 | 30 | [[annotations]] 31 | # managed by docs team 32 | path = ["help/**.page", "help/**.xml"] 33 | SPDX-FileCopyrightText = "GNOME documentation team " 34 | SPDX-License-Identifier = "CC-BY-SA-4.0" 35 | 36 | [[annotations]] 37 | # screenshots 38 | path = ["data/metainfo/**.png", "help/**.png"] 39 | SPDX-FileCopyrightText = "No rights reserved" 40 | SPDX-License-Identifier = "CC0-1.0" 41 | 42 | [[annotations]] 43 | path = "flatpak/org.gnome.Polari.json" 44 | SPDX-FileCopyrightText = "No rights reserved" 45 | SPDX-License-Identifier = "CC0-1.0" 46 | 47 | [[annotations]] 48 | path = "flatpak/**.patch" 49 | SPDX-FileCopyrightText = "Florian Müllner " 50 | SPDX-License-Identifier = "GPL-2.0-or-later" 51 | -------------------------------------------------------------------------------- /data/Polari.client: -------------------------------------------------------------------------------- 1 | [org.freedesktop.Telepathy.Client] 2 | Interfaces=org.freedesktop.Telepathy.Client.Handler;org.freedesktop.Telepathy.Client.Observer 3 | 4 | [org.freedesktop.Telepathy.Client.Handler.HandlerChannelFilter 0] 5 | org.freedesktop.Telepathy.Channel.ChannelType s=org.freedesktop.Telepathy.Channel.Type.Text 6 | org.freedesktop.Telepathy.Channel.TargetHandleType u=1 7 | 8 | [org.freedesktop.Telepathy.Client.Handler.HandlerChannelFilter 1] 9 | org.freedesktop.Telepathy.Channel.ChannelType s=org.freedesktop.Telepathy.Channel.Type.Text 10 | org.freedesktop.Telepathy.Channel.TargetHandleType u=2 11 | 12 | [org.freedesktop.Telepathy.Client.Handler.HandlerChannelFilter 2] 13 | org.freedesktop.Telepathy.Channel.ChannelType s=org.freedesktop.Telepathy.Channel.Type.ServerAuthentication 14 | org.freedesktop.Telepathy.Channel.Type.ServerAuthentication.AuthenticationMethod s=org.freedesktop.Telepathy.Channel.Interface.SASLAuthentication 15 | 16 | [org.freedesktop.Telepathy.Client.Observer.ObserverChannelFilter 0] 17 | org.freedesktop.Telepathy.Channel.ChannelType s=org.freedesktop.Telepathy.Channel.Type.Text 18 | org.freedesktop.Telepathy.Channel.TargetHandleType u=1 19 | 20 | [org.freedesktop.Telepathy.Client.Observer.ObserverChannelFilter 1] 21 | org.freedesktop.Telepathy.Channel.ChannelType s=org.freedesktop.Telepathy.Channel.Type.Text 22 | org.freedesktop.Telepathy.Channel.TargetHandleType u=2 23 | 24 | [org.freedesktop.Telepathy.Client.Handler.ObserverChannelFilter 2] 25 | org.freedesktop.Telepathy.Channel.ChannelType s=org.freedesktop.Telepathy.Channel.Type.ServerAuthentication 26 | org.freedesktop.Telepathy.Channel.Type.ServerAuthentication.AuthenticationMethod s=org.freedesktop.Telepathy.Channel.Interface.SASLAuthentication 27 | -------------------------------------------------------------------------------- /data/icons/REUSE.toml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2024 Florian Müllner 2 | # SPDX-License-Identifier: CC0-1.0 3 | version = 1 4 | 5 | [[annotations]] 6 | path = "hicolor/scalable/apps/org.gnome.Polari.svg" 7 | SPDX-FileCopyrightText = "2013 Sam Hewitt , 2018 Tobias Bernard " 8 | SPDX-License-Identifier = "LGPL-2.0-or-later" 9 | 10 | [[annotations]] 11 | path = "hicolor/symbolic/apps/org.gnome.Polari-symbolic.svg" 12 | SPDX-FileCopyrightText = "2015 Jakub Steiner " 13 | SPDX-License-Identifier = "LGPL-2.0-or-later" 14 | 15 | [[annotations]] 16 | path = "hicolor/scalable/apps/org.gnome.Polari.Devel.svg" 17 | SPDX-FileCopyrightText = "2021 Tobias Bernard " 18 | SPDX-License-Identifier = "LGPL-2.0-or-later" 19 | -------------------------------------------------------------------------------- /data/icons/hicolor/scalable/apps/org.gnome.Polari.Devel.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 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 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /data/icons/hicolor/scalable/apps/org.gnome.Polari.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /data/icons/hicolor/symbolic/apps/org.gnome.Polari-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 10 | 12 | 18 | 23 | 24 | 26 | 29 | 34 | 35 | 36 | 38 | 42 | 43 | 46 | 49 | 52 | 57 | 60 | 63 | 64 | 67 | 68 | 72 | 74 | 79 | 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /data/icons/meson.build: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2016 Florian Müllner 2 | # 3 | # SPDX-License-Identifier: GPL-2.0-or-later 4 | 5 | install_subdir('hicolor', install_dir: icondir) 6 | -------------------------------------------------------------------------------- /data/icons/org.gnome.Polari.Source.svg.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2021 Tobias Bernard 2 | 3 | SPDX-License-Identifier: LGPL-2.0-or-later 4 | -------------------------------------------------------------------------------- /data/meson.build: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2016 Florian Müllner 2 | # 3 | # SPDX-License-Identifier: GPL-2.0-or-later 4 | 5 | subdir('metainfo') 6 | subdir('icons') 7 | 8 | data_resources = gnome.compile_resources( 9 | 'data-resources', 10 | app_id + '.data.gresource.xml', 11 | dependencies: [metainfo], 12 | source_dir: ['.', meson.current_build_dir()], 13 | c_name: 'data_resources', 14 | ) 15 | 16 | lib_resources = gnome.compile_resources( 17 | 'lib-resources', 18 | app_id + '.lib.gresource.xml', 19 | c_name: 'lib_resources', 20 | ) 21 | 22 | desktop_filename = app_id + '.desktop' 23 | desktop_file = i18n.merge_file( 24 | input: desktop_filename + '.in', 25 | output: desktop_filename, 26 | po_dir: '../po', 27 | install: true, 28 | install_dir: desktopdir, 29 | type: 'desktop', 30 | ) 31 | 32 | if (desktop_file_validate.found()) 33 | test( 34 | 'Validating ' + desktop_filename, 35 | desktop_file_validate, 36 | args: [desktop_file.full_path()], 37 | workdir: meson.current_build_dir(), 38 | depends: [desktop_file], 39 | ) 40 | endif 41 | 42 | if (json_glib_validate.found()) 43 | test( 44 | 'Validating ' + 'networks.json', 45 | json_glib_validate, 46 | args: [files('resources/networks.json')], 47 | ) 48 | endif 49 | 50 | service_conf = configuration_data() 51 | service_conf.set('bindir', bindir) 52 | 53 | services = [ 54 | 'org.freedesktop.Telepathy.Client.Polari.service', 55 | app_id + '.service', 56 | ] 57 | 58 | foreach s : services 59 | configure_file( 60 | input: s + '.in', 61 | output: s, 62 | configuration: service_conf, 63 | install_dir: servicedir, 64 | ) 65 | endforeach 66 | 67 | install_data(app_id + '.gschema.xml', install_dir: schemadir) 68 | install_data('Polari.client', install_dir: tpclientdir) 69 | -------------------------------------------------------------------------------- /data/metainfo/meson.build: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2016 Florian Müllner 2 | # 3 | # SPDX-License-Identifier: GPL-2.0-or-later 4 | 5 | metainfo = i18n.merge_file( 6 | input: app_id + '.metainfo.xml.in', 7 | output: '@BASENAME@', 8 | po_dir: '../../po', 9 | install: true, 10 | install_dir: metainfodir, 11 | ) 12 | 13 | if (appstreamcli.found()) 14 | test( 15 | 'Validating metainfo', 16 | appstreamcli, 17 | args: ['validate', '--no-net', '--explain', metainfo.full_path()], 18 | workdir: meson.current_build_dir(), 19 | depends: [metainfo], 20 | ) 21 | endif 22 | -------------------------------------------------------------------------------- /data/metainfo/org.gnome.Polari.metainfo.xml.in: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 10 | org.gnome.Polari.desktop 11 | 12 | Polari 13 | Talk to people on IRC 14 | 15 | CC0-1.0 16 | GPL-2.0-or-later 17 | 18 | https://apps.gnome.org/Polari 19 | https://gitlab.gnome.org/GNOME/polari/issues/ 20 | https://gitlab.gnome.org/GNOME/polari/ 21 | https://welcome.gnome.org/app/Polari 22 | https://help.gnome.org/users/polari/stable/ 23 | https://www.gnome.org/donate/ 24 | https://l10n.gnome.org/module/polari/ 25 | 26 | GNOME 27 | 28 | Florian Müllner 29 | 30 | Florian Müllner 31 | 32 | fmuellner@gnome.org 33 | 34 | polari 35 | org.gnome.Polari.desktop 36 | 37 | 38 | HiDpiIcon 39 | HighContrast 40 | ModernToolkit 41 | Notifications 42 | UserDocs 43 | 44 | 45 | 46 | #8ff0a4 47 | #275942 48 | 49 | 50 | 51 | 360 52 | 53 | 54 | 55 | pointing 56 | keyboard 57 | touch 58 | 59 | 60 | 61 | intense 62 | intense 63 | 64 | 65 | 66 |

67 | A simple Internet Relay Chat (IRC) client that is designed to integrate 68 | seamlessly with GNOME; it features a simple and beautiful interface which 69 | allows you to focus on your conversations. 70 |

71 |

72 | You can use Polari to publicly chat with people in a channel, and to 73 | have private one-to-one conversations. Notifications make sure that 74 | you never miss an important message. 75 |

76 |
77 | 78 | 79 | 80 | 81 |

Store chat logs in a private database.

82 |

Use adaptive dialogs.

83 |
84 |
85 |
86 | 87 | 88 | 89 | https://gitlab.gnome.org/GNOME/polari/raw/HEAD/data/metainfo/polari-main.png 90 | 91 | 92 | https://gitlab.gnome.org/GNOME/polari/raw/HEAD/data/metainfo/polari-user-list.png 93 | 94 | 95 | https://gitlab.gnome.org/GNOME/polari/raw/HEAD/data/metainfo/polari-join-dialog.png 96 | 97 | 98 | https://gitlab.gnome.org/GNOME/polari/raw/HEAD/data/metainfo/polari-connection.png 99 | 100 | 101 | https://gitlab.gnome.org/GNOME/polari/raw/HEAD/data/metainfo/polari-connection-properties.png 102 | 103 | 104 |
105 | -------------------------------------------------------------------------------- /data/metainfo/polari-connection-properties.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GNOME/polari/70b019c47bba9b8abf77ac17b2b15b5551b8c2e9/data/metainfo/polari-connection-properties.png -------------------------------------------------------------------------------- /data/metainfo/polari-connection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GNOME/polari/70b019c47bba9b8abf77ac17b2b15b5551b8c2e9/data/metainfo/polari-connection.png -------------------------------------------------------------------------------- /data/metainfo/polari-join-dialog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GNOME/polari/70b019c47bba9b8abf77ac17b2b15b5551b8c2e9/data/metainfo/polari-join-dialog.png -------------------------------------------------------------------------------- /data/metainfo/polari-main.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GNOME/polari/70b019c47bba9b8abf77ac17b2b15b5551b8c2e9/data/metainfo/polari-main.png -------------------------------------------------------------------------------- /data/metainfo/polari-user-list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GNOME/polari/70b019c47bba9b8abf77ac17b2b15b5551b8c2e9/data/metainfo/polari-user-list.png -------------------------------------------------------------------------------- /data/org.freedesktop.Telepathy.Client.Polari.service.in: -------------------------------------------------------------------------------- 1 | [D-BUS Service] 2 | Name=org.freedesktop.Telepathy.Client.Polari 3 | Exec=@bindir@/polari --start-client 4 | -------------------------------------------------------------------------------- /data/org.gnome.Polari.data.gresource.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 10 | 11 | resources/style.css 12 | metainfo/org.gnome.Polari.metainfo.xml 13 | 14 | resources/networks.json 15 | 16 | resources/help-overlay.ui 17 | 18 | resources/people-symbolic.svg 19 | resources/polari-user-notify-symbolic.svg 20 | 21 | resources/connection-details.ui 22 | resources/connection-properties.ui 23 | resources/entry-area.ui 24 | resources/join-room-dialog.ui 25 | resources/main-window.ui 26 | resources/nick-popover.ui 27 | resources/room-list-header.ui 28 | resources/room-list-row.ui 29 | resources/server-room-list.ui 30 | resources/user-details.ui 31 | resources/user-popover.ui 32 | resources/initial-setup-window.ui 33 | 34 | 35 | -------------------------------------------------------------------------------- /data/org.gnome.Polari.desktop.in: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Name=Polari 3 | Comment=An Internet Relay Chat Client for GNOME 4 | Exec=gapplication launch org.gnome.Polari %U 5 | # Translators: Do NOT translate or transliterate this text (this is an icon file name)! 6 | Icon=org.gnome.Polari 7 | Type=Application 8 | MimeType=x-scheme-handler/irc; 9 | StartupNotify=true 10 | DBusActivatable=true 11 | X-GNOME-UsesNotifications=true 12 | Categories=GNOME;GTK;Network;IRCClient; 13 | # Translators: Search terms to find this application. Do NOT translate or localize the semicolons! The list MUST also end with a semicolon! 14 | Keywords=IRC;Internet;Relay;Chat; 15 | -------------------------------------------------------------------------------- /data/org.gnome.Polari.gschema.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 10 | 11 | 12 | [] 13 | Saved channel list 14 | List of channels to restore on startup 15 | 16 | 17 | false 18 | Run in Background 19 | Keep running in background when closed. 20 | 21 | 22 | 800 23 | Window width 24 | 25 | 26 | 500 27 | Window height 28 | 29 | 30 | false 31 | Window maximized 32 | Window maximized state 33 | 34 | 35 | {} 36 | Last active channel 37 | Last active (selected) channel 38 | 39 | 40 | 41 | 42 | 43 | "NickServ" 44 | Identify botname 45 | Nickname of the bot to identify with 46 | 47 | 48 | "identify" 49 | Identify command 50 | Command used to identify with bot 51 | 52 | 53 | "" 54 | Identify username 55 | Username to use in identify command 56 | 57 | 58 | false 59 | Identify username supported 60 | 61 | Whether the identify command is known to support the username parameter 62 | 63 | 64 | 65 | [] 66 | List of muted usernames 67 | 68 | A list of usernames for whose private messages not to show notifications 69 | 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /data/org.gnome.Polari.lib.gresource.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 10 | 11 | resources/polari.ontology 12 | resources/sparql/get-room-events.rq 13 | 14 | 15 | -------------------------------------------------------------------------------- /data/org.gnome.Polari.service.in: -------------------------------------------------------------------------------- 1 | [D-BUS Service] 2 | Name=org.gnome.Polari 3 | Exec=@bindir@/polari --gapplication-service 4 | -------------------------------------------------------------------------------- /data/resources/connection-details.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 11 | 56 | 57 | -------------------------------------------------------------------------------- /data/resources/connection-properties.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 10 | 76 | 77 | -------------------------------------------------------------------------------- /data/resources/entry-area.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 10 | 132 | 133 | -------------------------------------------------------------------------------- /data/resources/help-overlay.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 10 | 11 | True 12 | 13 | 14 | 16 | 10 17 | 18 | 19 | General 20 | 21 | 22 | Join Room 23 | <Primary>n 24 | 25 | 26 | 27 | 28 | Leave Room 29 | <Primary>w 30 | 31 | 32 | 33 | 34 | Show Userlist 35 | F9 36 | 37 | 38 | 39 | 40 | Show Emoji Picker 41 | <Primary>e 42 | 43 | 44 | 45 | 46 | Show Help 47 | F1 48 | 49 | 50 | 51 | 52 | Open Menu 53 | F10 54 | 55 | 56 | 57 | 58 | Quit 59 | <Primary>q 60 | 61 | 62 | 63 | 64 | Keyboard Shortcuts 65 | <Primary>question 66 | 67 | 68 | 69 | 70 | 71 | 72 | Navigation 73 | 74 | 75 | Next Room 76 | <Primary>Page_Down 77 | 78 | 79 | 80 | 81 | Previous Room 82 | <Primary>Page_Up 83 | 84 | 85 | 86 | 87 | Next Room with Unread Messages 88 | <Primary><Shift>Page_Down 89 | 90 | 91 | 92 | 93 | Previous Room with Unread Messages 94 | <Primary><Shift>Page_Up 95 | 96 | 97 | 98 | 99 | First Room 100 | <Primary>Home 101 | 102 | 103 | 104 | 105 | Last Room 106 | <Primary>End 107 | 108 | 109 | 110 | 111 | First – Ninth Room 112 | <Alt>1...9 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | -------------------------------------------------------------------------------- /data/resources/initial-setup-window.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 11 | 102 | 103 | -------------------------------------------------------------------------------- /data/resources/nick-popover.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 10 | 47 | 48 | -------------------------------------------------------------------------------- /data/resources/people-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /data/resources/people-symbolic.svg.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: The GNOME Design Team 2 | 3 | SPDX-License-Identifier: CC0-1.0 4 | -------------------------------------------------------------------------------- /data/resources/polari-user-notify-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | image/svg+xml 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /data/resources/polari-user-notify-symbolic.svg.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: The GNOME Design Team 2 | 3 | SPDX-License-Identifier: CC0-1.0 4 | -------------------------------------------------------------------------------- /data/resources/polari.ontology: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2017 Carlos Garnacho 2 | # SPDX-FileCopyrightText: 2017 Florian Müllner 3 | # 4 | # SPDX-License-Identifier: GPL-2.0-or-later 5 | 6 | @prefix rdf: . 7 | @prefix rdfs: . 8 | @prefix xsd: . 9 | @prefix nrl: . 10 | @prefix polari: . 11 | 12 | polari: a nrl:Namespace, nrl:Ontology ; 13 | nrl:prefix "polari" ; 14 | rdfs:comment "Ontology for Polari chat logs" ; 15 | nrl:lastModified "2017-07-22T02:35:00Z" . 16 | 17 | # Account 18 | polari:Account a rdfs:Class ; 19 | rdfs:subClassOf rdfs:Resource . 20 | 21 | polari:id a rdf:Property ; 22 | nrl:maxCardinality 1 ; 23 | rdfs:domain polari:Account ; 24 | rdfs:range xsd:string . 25 | 26 | # Contact 27 | polari:Contact a rdfs:Class ; 28 | rdfs:subClassOf rdfs:Resource . 29 | 30 | polari:nick a rdf:Property ; 31 | nrl:maxCardinality 1 ; 32 | rdfs:domain polari:Contact ; 33 | rdfs:range xsd:string . 34 | 35 | # User's own contact 36 | polari:SelfContact a rdfs:Class ; 37 | rdfs:subClassOf polari:Contact . 38 | 39 | # Channel 40 | polari:Channel a rdfs:Class ; 41 | rdfs:subClassOf rdfs:Resource . 42 | 43 | polari:name a rdf:Property ; 44 | nrl:maxCardinality 1 ; 45 | rdfs:domain polari:Channel ; 46 | rdfs:range xsd:string . 47 | 48 | polari:account a rdf:Property ; 49 | rdfs:domain polari:Channel ; 50 | rdfs:range polari:Account . 51 | 52 | # Room 53 | polari:Room a rdfs:Class ; 54 | rdfs:subClassOf polari:Channel . 55 | 56 | # Conversation 57 | polari:Conversation a rdfs:Class ; 58 | rdfs:subClassOf polari:Channel . 59 | 60 | polari:target a rdf:Property ; 61 | nrl:maxCardinality 1 ; 62 | rdfs:domain polari:Conversation ; 63 | rdfs:range polari:Contact . 64 | 65 | # Message 66 | polari:Message a rdfs:Class ; 67 | rdfs:subClassOf rdfs:Resource . 68 | 69 | polari:channel a rdf:Property ; 70 | nrl:maxCardinality 1 ; 71 | rdfs:domain polari:Message ; 72 | rdfs:range polari:Channel . 73 | 74 | polari:sender a rdf:Property ; 75 | nrl:maxCardinality 1 ; 76 | rdfs:domain polari:Message ; 77 | rdfs:range polari:Contact . 78 | 79 | polari:time a rdf:Property ; 80 | nrl:maxCardinality 1 ; 81 | rdfs:domain polari:Message ; 82 | rdfs:range xsd:dateTime ; 83 | nrl:indexed true . 84 | 85 | polari:text a rdf:Property ; 86 | nrl:maxCardinality 1 ; 87 | rdfs:domain polari:Message ; 88 | rdfs:range xsd:string ; 89 | nrl:fulltextIndexed true . 90 | 91 | polari:isAction a rdf:Property ; 92 | nrl:maxCardinality 1 ; 93 | rdfs:domain polari:Message ; 94 | rdfs:range xsd:boolean . 95 | -------------------------------------------------------------------------------- /data/resources/room-list-header.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 11 | 101 | 102 | 105 | 106 | 107 | vertical 108 | 12 109 | 12 110 | 12 111 | 12 112 | 113 | 114 | True 115 | 30 116 | 15 117 | 0 118 | 6 119 | 6 120 | 121 | 122 | 123 | 124 | True 125 | 30 126 | 0 127 | 6 128 | 6 129 | 130 | 131 | 132 | 133 | 135 | False 136 | True 137 | 6 138 | 6 139 | 6 140 | 141 | 142 | 143 | 144 | 6 145 | 6 146 | 147 | 148 | 149 | 150 | Connect 151 | 152 | 153 | 154 | 155 | Reconnect 156 | 157 | 158 | 159 | 160 | Disconnect 161 | 162 | 163 | 164 | 165 | Remove 166 | 167 | 168 | 169 | 170 | Properties 171 | 172 | 173 | 174 | 175 | 176 | 177 | -------------------------------------------------------------------------------- /data/resources/room-list-row.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 11 | 77 | 78 | -------------------------------------------------------------------------------- /data/resources/server-room-list.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | + 24 | True 25 | 26 | 27 | 28 | 29 | store 30 | 31 | 124 | 125 | -------------------------------------------------------------------------------- /data/resources/sparql/get-room-events.rq: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2015 Carlos Garnacho 2 | # 3 | # SPDX-License-Identifier: GPL-2.0-or-later 4 | 5 | # Inputs: channel, lastTime 6 | # Outputs: text, senderNick, time, isAction, isSelf 7 | SELECT 8 | ?text 9 | ?senderNick 10 | ?time 11 | ?isAction 12 | ?isSelf 13 | { 14 | GRAPH polari:irc { 15 | ?msg a polari:Message; 16 | polari:time ?time; 17 | polari:sender ?sender; 18 | polari:text ?text; 19 | polari:channel ~channel. 20 | 21 | ?sender polari:nick ?senderNick . 22 | 23 | OPTIONAL { ?msg polari:isAction ?isAction }. 24 | BIND (EXISTS { ?sender a polari:SelfContact } AS ?isSelf). 25 | # Provide a lower boundary for the queried time range, 1y worth of time 26 | BIND (~lastTime^^xsd:dateTime - (60 * 60 * 24 * 365) AS ?after). 27 | FILTER (?time < ~lastTime^^xsd:dateTime && ?time > ?after). 28 | } 29 | } 30 | ORDER BY DESC(?time) DESC(tracker:id(?msg)) 31 | -------------------------------------------------------------------------------- /data/resources/style.css: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2013 Florian Müllner 3 | * SPDX-FileCopyrightText: 2013 Jakub Steiner 4 | * SPDX-FileCopyrightText: 2015 Bastian Ilsø 5 | * SPDX-FileCopyrightText: 2016 Lapo Calamandrei 6 | * SPDX-FileCopyrightText: 2016 Danny Mølgaard 7 | * SPDX-FileCopyrightText: 2019 daronion 8 | * 9 | * SPDX-License-Identifier: GPL-2.0-or-later 10 | */ 11 | 12 | @define-color polari_dark_bg_color shade(@window_bg_color, 0.95); 13 | 14 | @define-color active_nick_color @accent_bg_color; 15 | @define-color active_nick_hover_color lighter(@active_nick_color); 16 | 17 | @define-color inactive_nick_color alpha(@view_fg_color, 0.55); 18 | @define-color inactive_nick_hover_color lighter(@inactive_nick_color); 19 | 20 | .info { margin-bottom: 1px; } 21 | 22 | .polari-active-nick { color: @active_nick_color; } 23 | .polari-active-nick:hover { color: @active_nick_hover_color; } 24 | 25 | .polari-inactive-nick { color: @inactive_nick_color; } 26 | .polari-inactive-nick:hover { color: @inactive_nick_hover_color; } 27 | 28 | .polari-user-list row.expanded { 29 | background-color: @content_view_bg; 30 | } 31 | 32 | .polari-user-list row frame { 33 | box-shadow: inset 0 2px 4px alpha(@polari_dark_bg_color, 0.8); 34 | transition: all 250ms ease-out; 35 | border-width: 0; 36 | } 37 | 38 | .polari-user-list .placeholder image { 39 | color: alpha(currentColor, 0.3); 40 | } 41 | 42 | .polari-user-list .placeholder label { 43 | font-size: 1.2em; 44 | color: alpha(currentColor, 0.6); 45 | } 46 | 47 | .polari-paste-confirmation { 48 | padding: 6px; 49 | } 50 | .polari-paste-confirmation:dir(ltr) { 51 | padding-right: 0; 52 | } 53 | .polari-paste-confirmation:dir(rtl) { 54 | padding-left: 0; 55 | } 56 | 57 | .polari-nick-button { 58 | padding-left: 8px; 59 | padding-right: 8px; 60 | } 61 | 62 | .polari-nick-entry { 63 | color: alpha(@window_fg_color, 0.6); 64 | } 65 | 66 | .polari-room-list .room-list-header { 67 | font-size: smaller; 68 | padding: 8px 7px 0; 69 | min-height: 24px; 70 | } 71 | 72 | .polari-room-list .room-list-header label:dir(ltr), 73 | .polari-room-list .room-list-header stack:dir(rtl) { 74 | padding-left: 8px; 75 | } 76 | 77 | .polari-room-list .room-list-header stack:dir(ltr), 78 | .polari-room-list .room-list-header label:dir(rtl) { 79 | padding-right: 8px; 80 | } 81 | 82 | .polari-room-list .room-list-header separator { 83 | background-color: alpha(@borders, 0.55); 84 | } 85 | 86 | .polari-room-list row { 87 | padding-left: 8px; 88 | } 89 | 90 | .polari-room-list row:not(.room-list-header):not(.inactive):not(.muted) { 91 | font-weight: bold; 92 | } 93 | 94 | .polari-room-list row.inactive, 95 | .polari-room-list row.inactive:backdrop { 96 | color: mix(@window_fg_color, @view_bg_color, 0.3); 97 | } 98 | 99 | .polari-room-list row:backdrop { 100 | color: @window_fg_color; 101 | } 102 | 103 | .polari-room-list row .pending-messages-count { 104 | color: @accent_fg_color; 105 | background-color: mix(@accent_bg_color, @accent_fg_color, 0.3); 106 | font-size: smaller; 107 | border-radius: 4px; 108 | padding: 0px 4px; 109 | } 110 | 111 | .polari-room-list row .pending-messages-count:backdrop { 112 | color: @accent_fg_color; 113 | background-color: mix(@accent_bg_color, @accent_fg_color, 0.1); 114 | } 115 | 116 | treeview.polari-server-room-list { 117 | padding: 6px 12px; 118 | } 119 | 120 | .url-preview { padding: 8px; } 121 | .url-preview { margin: 12px; } 122 | .url-preview:dir(ltr) { margin-left: 0; } 123 | .url-preview:dir(rtl) { margin-right: 0; } 124 | .url-preview image { min-width: 120px; min-height: 90px; } 125 | .url-preview label { font-size: small; } 126 | 127 | .emoji-picker entry { margin: 6px; } 128 | 129 | .emoji-picker .view { background-color: @window_bg_color; } 130 | 131 | .emoji-picker undershoot { border: 0 solid @borders; } 132 | .emoji-picker undershoot:backdrop { border: 0 solid @unfocused_borders; } 133 | 134 | .emoji-picker undershoot.bottom { border-bottom-width: 1px; } 135 | .emoji-picker undershoot.top { border-top-width: 1px; } 136 | 137 | .emoji widget { border-radius: 6px; } 138 | .emoji label { padding: 6px; } 139 | 140 | .polari-setup-page { 141 | padding: 0 0 48px 0; 142 | } 143 | 144 | .polari-error-information { 145 | background-color: @polari_dark_bg_color; 146 | } 147 | .polari-error-information GtkLabel { 148 | color: @window_fg_color; 149 | font-size: smaller; 150 | } 151 | 152 | .polari-listbox-filterbar { 153 | border: solid @borders; 154 | border-width: 0 0 1px; 155 | } 156 | 157 | /* "opt-out" of insensitive view styling */ 158 | .polari-entry-area:disabled { background-color: @view_bg_color; } 159 | 160 | /* tweak labels in the user popover */ 161 | .polari-user-popover-nick { font-weight: bold; } 162 | .polari-user-popover-status { font-size: smaller; } 163 | 164 | .polari-room-loading { 165 | opacity: 0.3; 166 | } 167 | 168 | /* Differentiate test instance from "normal" ones */ 169 | window.test-instance .content-pane headerbar { 170 | background-image: cross-fade(25% -gtk-icontheme('system-run-symbolic')); 171 | background-repeat: no-repeat; 172 | background-position: 1em center; 173 | background-size: 4em; 174 | } 175 | 176 | /* Differentiate snapshot builds from regular ones */ 177 | window.snapshot headerbar { background: none; } 178 | window.snapshot .titlebar { 179 | background: linear-gradient(to left, #a5b1bd 0%, #a5b1bd 8%, @window_bg_color 25%); 180 | box-shadow: inset 0 1px #f1f3f5; 181 | border-color: #909fae; 182 | color: alpha(@window_fg_color, 0.4); 183 | } 184 | -------------------------------------------------------------------------------- /data/resources/user-popover.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 10 | 79 | 80 | -------------------------------------------------------------------------------- /flatpak/idle-build-fix.patch: -------------------------------------------------------------------------------- 1 | From 99704fb9214cbc1319f7faea129ea4b97fda1c97 Mon Sep 17 00:00:00 2001 2 | From: =?UTF-8?q?Florian=20M=C3=BCllner?= 3 | Date: Sat, 24 Jul 2021 04:43:28 +0200 4 | Subject: [PATCH] meson: Built helper libraries statically 5 | 6 | They aren't installed, so the dynamic linker has no chance of 7 | locating them if they are built as shared libraries. 8 | --- 9 | extensions/meson.build | 2 +- 10 | src/meson.build | 2 +- 11 | 2 files changed, 2 insertions(+), 2 deletions(-) 12 | 13 | diff --git a/extensions/meson.build b/extensions/meson.build 14 | index 388820d..78f11a5 100644 15 | --- a/extensions/meson.build 16 | +++ b/extensions/meson.build 17 | @@ -5,7 +5,7 @@ xmls = files( 18 | 19 | subdir('_gen') 20 | 21 | -libidle_extensions = library( 22 | +libidle_extensions = static_library( 23 | 'idle-extensions', 24 | sources: [ 25 | 'extensions.h', 26 | diff --git a/src/meson.build b/src/meson.build 27 | index 0cfcc3a..25a599c 100644 28 | --- a/src/meson.build 29 | +++ b/src/meson.build 30 | @@ -1,4 +1,4 @@ 31 | -libidle_convenience = library( 32 | +libidle_convenience = static_library( 33 | 'idle-convenience', 34 | sources: [ 35 | 'idle-connection.c', 36 | -- 37 | 2.31.1 38 | 39 | -------------------------------------------------------------------------------- /flatpak/org.gnome.Polari.json: -------------------------------------------------------------------------------- 1 | { 2 | "app-id": "org.gnome.Polari", 3 | "runtime": "org.gnome.Platform", 4 | "runtime-version": "master", 5 | "sdk": "org.gnome.Sdk", 6 | "command": "polari", 7 | "x-run-args": [ 8 | "--test-instance" 9 | ], 10 | "tags": [ 11 | "nightly" 12 | ], 13 | "desktop-file-name-prefix": "(Nightly) ", 14 | "finish-args": [ 15 | "--share=ipc", 16 | "--socket=fallback-x11", 17 | "--socket=wayland", 18 | "--share=network", 19 | "--device=dri", 20 | "--own-name=org.freedesktop.Telepathy.Client.Polari", 21 | "--own-name=org.freedesktop.Telepathy.Client.Polari.*", 22 | "--talk-name=org.freedesktop.Telepathy.AccountManager", 23 | "--talk-name=org.freedesktop.Telepathy.ChannelDispatcher", 24 | "--talk-name=org.freedesktop.Telepathy.Connection.idle.irc.*", 25 | "--talk-name=org.freedesktop.Telepathy.Client.GnomeShell.*", 26 | "--own-name=org.freedesktop.Telepathy.Client.TpGLibRequestAndHandle.*", 27 | "--own-name=org.freedesktop.Telepathy.AccountManager", 28 | "--own-name=org.freedesktop.Telepathy.ChannelDispatcher", 29 | "--own-name=org.freedesktop.Telepathy.MissionControl5", 30 | "--own-name=org.freedesktop.Telepathy.ConnectionManager.idle", 31 | "--own-name=org.freedesktop.Telepathy.Connection.idle.irc.*", 32 | "--own-name=org.freedesktop.Telepathy.Client.Logger", 33 | "--talk-name=org.freedesktop.secrets", 34 | "--filesystem=~/.local/share/TpLogger", 35 | "--env=TPL_LOG_DIR=.local/share", 36 | "--metadata=X-DConf=migrate-path=/org/gnome/polari/" 37 | ], 38 | "build-options": { 39 | "cflags": "-O2 -g" 40 | }, 41 | "cleanup": [ 42 | "*.la", 43 | "/include", 44 | "/lib/pkgconfig", 45 | "/share/gir-1.0", 46 | "/share/man", 47 | "/share/polari/gir-1.0", 48 | "/share/telepathy" 49 | ], 50 | "modules": [ 51 | { 52 | "name": "dbus-glib", 53 | "config-opts": [ 54 | "--disable-static", 55 | "--disable-gtk-doc" 56 | ], 57 | "sources": [ 58 | { 59 | "type": "git", 60 | "url": "https://gitlab.freedesktop.org/dbus/dbus-glib.git", 61 | "tag": "dbus-glib-0.112", 62 | "commit": "f16a4ec9a37d067aea4772ce35ae5b88d9416cab", 63 | "x-checker-data": { 64 | "type": "git", 65 | "tag-pattern": "^dbus-glib-([\\d.]+)$" 66 | } 67 | } 68 | ] 69 | }, 70 | { 71 | "name": "telepathy-glib", 72 | "config-opts": [ 73 | "--disable-static", 74 | "--disable-gtk-doc" 75 | ], 76 | "sources": [ 77 | { 78 | "type": "git", 79 | "url": "https://www.github.com/TelepathyIM/telepathy-glib.git", 80 | "tag": "telepathy-glib-0.24.2", 81 | "commit": "424e12a3277d23bc8cb3e84a1659a0939ade819e", 82 | "x-checker-data": { 83 | "type": "git", 84 | "tag-pattern": "^telepathy-glib-(\\d+.[^(99)].[\\d.]+)$" 85 | } 86 | }, 87 | { 88 | "type": "patch", 89 | "path": "tp-change-uniquify.patch" 90 | }, 91 | { 92 | "type": "patch", 93 | "path": "tp-glib-casts.patch" 94 | } 95 | ] 96 | }, 97 | { 98 | "name": "telepathy-mission-control", 99 | "config-opts": [ 100 | "--disable-static", 101 | "--disable-gtk-doc" 102 | ], 103 | "sources": [ 104 | { 105 | "type": "git", 106 | "url": "https://www.github.com/TelepathyIM/telepathy-mission-control.git", 107 | "tag": "telepathy-mission-control-5.16.6", 108 | "commit": "67df52b68b28e2a3755702b669a58747e21dd12b", 109 | "x-checker-data": { 110 | "type": "git", 111 | "tag-pattern": "^telepathy-mission-control-(\\d+.[^(99)].[\\d.]+)$" 112 | } 113 | } 114 | ], 115 | "cleanup": [ 116 | "/bin", 117 | "/share/dbus-1" 118 | ] 119 | }, 120 | { 121 | "name": "telepathy-idle", 122 | "buildsystem": "meson", 123 | "builddir": true, 124 | "config-opts": [ 125 | "-Dtwisted_tests=false" 126 | ], 127 | "sources": [ 128 | { 129 | "type": "git", 130 | "url": "https://www.github.com/TelepathyIM/telepathy-idle.git", 131 | "tag": "telepathy-idle-0.2.2", 132 | "commit": "02d03c57cb5f061e374fe375c9b82f3c826cb538", 133 | "x-checker-data": { 134 | "type": "git", 135 | "tag-pattern": "^telepathy-idle-(\\d+.[^(99)].[\\d.]+)$" 136 | } 137 | }, 138 | { 139 | "type": "patch", 140 | "path": "idle-build-fix.patch" 141 | } 142 | ], 143 | "cleanup": [ 144 | "/share/dbus-1" 145 | ] 146 | }, 147 | { 148 | "name": "tinysparql", 149 | "buildsystem": "meson", 150 | "cleanup": ["/etc", "/lib/systemd"], 151 | "config-opts": ["--libdir=lib", "-Dbash_completion=false", 152 | "-Dsystemd_user_services=false", 153 | "-Dman=false", "-Ddocs=false", 154 | "-Dtests=false"], 155 | "sources": [ 156 | { 157 | "type": "git", 158 | "url": "https://gitlab.gnome.org/GNOME/tinysparql.git" 159 | } 160 | ] 161 | }, 162 | { 163 | "name": "polari", 164 | "buildsystem": "meson", 165 | "builddir": true, 166 | "config-opts": [ 167 | "--libdir=lib" 168 | ], 169 | "sources": [ 170 | { 171 | "type": "git", 172 | "url": "https://gitlab.gnome.org/GNOME/polari.git" 173 | } 174 | ] 175 | } 176 | ] 177 | } 178 | -------------------------------------------------------------------------------- /flatpak/tp-change-uniquify.patch: -------------------------------------------------------------------------------- 1 | From fd733c82ba71201ab7793a38e8fc2fb47a9062f9 Mon Sep 17 00:00:00 2001 2 | From: =?UTF-8?q?Florian=20M=C3=BCllner?= 3 | Date: Sun, 19 Feb 2017 00:40:05 +0100 4 | Subject: [PATCH] base-client: Append a single element when uniquifying name 5 | 6 | --- 7 | telepathy-glib/base-client.c | 2 +- 8 | 1 file changed, 1 insertion(+), 1 deletion(-) 9 | 10 | diff --git a/telepathy-glib/base-client.c b/telepathy-glib/base-client.c 11 | index 600f292..7f5c155 100644 12 | --- a/telepathy-glib/base-client.c 13 | +++ b/telepathy-glib/base-client.c 14 | @@ -1347,7 +1347,7 @@ tp_base_client_constructed (GObject *object) 15 | unique = tp_escape_as_identifier (tp_dbus_daemon_get_unique_name ( 16 | self->priv->dbus)); 17 | 18 | - g_string_append_printf (string, ".%s.n%u", unique, unique_counter++); 19 | + g_string_append_printf (string, ".%sn%u", unique, unique_counter++); 20 | g_free (unique); 21 | } 22 | 23 | -- 24 | 2.9.3 25 | 26 | -------------------------------------------------------------------------------- /flatpak/tp-glib-casts.patch: -------------------------------------------------------------------------------- 1 | From ccc5a845d439778d98803d35f1c2557f48325194 Mon Sep 17 00:00:00 2001 2 | From: Georges Basile Stavracas Neto 3 | Date: Fri, 5 Jul 2024 13:02:19 -0300 4 | Subject: [PATCH] telepathy-glib: Fix casts 5 | 6 | Signed-off-by: Georges Basile Stavracas Neto 7 | --- 8 | telepathy-glib/protocol.c | 2 +- 9 | 1 file changed, 1 insertion(+), 1 deletion(-) 10 | 11 | diff --git a/telepathy-glib/protocol.c b/telepathy-glib/protocol.c 12 | index 88fdff382..25b36620b 100644 13 | --- a/telepathy-glib/protocol.c 14 | +++ b/telepathy-glib/protocol.c 15 | @@ -1791,7 +1791,7 @@ _tp_protocol_parse_manager_file (GKeyFile *file, 16 | i++; 17 | } 18 | 19 | - param_specs = g_ptr_array_new_full (i, tp_value_array_free); 20 | + param_specs = g_ptr_array_new_full (i, (GDestroyNotify) tp_value_array_free); 21 | 22 | for (key = keys; key != NULL && *key != NULL; key++) 23 | { 24 | -- 25 | 2.45.2 26 | 27 | -------------------------------------------------------------------------------- /help/C/images/Polari.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GNOME/polari/70b019c47bba9b8abf77ac17b2b15b5551b8c2e9/help/C/images/Polari.png -------------------------------------------------------------------------------- /help/C/images/polari-paste.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GNOME/polari/70b019c47bba9b8abf77ac17b2b15b5551b8c2e9/help/C/images/polari-paste.png -------------------------------------------------------------------------------- /help/C/images/polari-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GNOME/polari/70b019c47bba9b8abf77ac17b2b15b5551b8c2e9/help/C/images/polari-screenshot.png -------------------------------------------------------------------------------- /help/C/index.page: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | Polari 8 | 9 | 10 | 11 | 12 | Ankit R Gadiya 13 | ankit4922@gmail.com 14 | 2016 15 | 16 | 17 | 18 | Paul Cutler 19 | pcutler@gnome.org 20 | 21 | 22 | 23 |

Creative Commons Share Alike 4.0

24 |
25 | 26 | 27 | Polari is a clean, simple, and minimal Internet Relay Chat 28 | (IRC) client which integrates with the GNOME desktop. 29 | Learn more about Polari. 30 |
31 | 32 | 33 | <media type="image" mime="image/png" its:translate="no" src="images/Polari.png"/> 34 | Polari 35 | 36 | 37 |
38 | Connecting to IRC Servers and Chat Rooms 39 |
40 | 41 |
42 | Conversations 43 |
44 | 45 | 46 |
47 | -------------------------------------------------------------------------------- /help/C/introduction.page: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | Ankit R Gadiya 13 | ankit4922@gmail.com 14 | 2016 15 | 16 | 17 | 18 | Paul Cutler 19 | pcutler@gnome.org 20 | 2017 21 | 22 | 23 | 24 | 25 | An introduction to Polari, a clean, minimal IRC client. 26 | 27 | 28 | 29 | Introduction 30 | 31 |

32 | Polari is a simple Internet Relay Chat (IRC) client which 33 | integrates with the GNOME desktop and enables you to chat with people 34 | around the world through large chatrooms or private messaging. 35 |

36 | 37 | 38 |

Polari IRC Client

39 |
40 | 41 |
42 | -------------------------------------------------------------------------------- /help/C/irc-commands.page: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | The supported IRC commands. 9 | 10 | 11 | Milo Casagrande 12 | milo@ubuntu.com 13 | 14 | 15 | Paul Cutler 16 | pcutler@gnome.org 17 | 18 | 19 |

Creative Commons Share Alike 4.0

20 |
21 |
22 | 23 | Supported IRC commands 24 |

25 | To see the list of the supported IRC commands, in a chat room type 26 | /help and press Enter. 27 |

28 | 29 |

30 | All commands available will be listed and you can type 31 | /help commandname to get additional help with a specific 32 | command. 33 |

34 |
35 |
36 | -------------------------------------------------------------------------------- /help/C/irc-join-room.page: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | Join an IRC channel. 11 | 12 | 13 | Paul Cutler 14 | pcutler@gnome.org 15 | 16 | 17 |

Creative Commons Share Alike 4.0

18 |
19 |
20 | 21 | Join an IRC chat room 22 | 23 |

You can join IRC chat rooms (also known as IRC channels) on any IRC 24 | network you are connected to. To connect to an IRC network, 25 | see .

26 | 27 |

To join an IRC chat room:

28 | 29 | 30 | 31 |

Press + in the top-left corner.

32 |
33 | 34 |

Enter the name of the chat room you would like to join or select 35 | the chat room(s) from the list and then press Join. 36 | You can select multiple rooms from the list to join.

37 |
38 | 39 |

Click the Join button to connect to the chat room or rooms. 40 |

41 |
42 |
43 |
44 | -------------------------------------------------------------------------------- /help/C/irc-nick-password.page: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | Protect your nickname to prevent other IRC users from using it. 11 | 12 | 13 | Milo Casagrande 14 | milo@ubuntu.com 15 | 16 | 17 | Paul Cutler 18 | pcutler@gnome.org 19 | 20 | 21 |

Creative Commons Share Alike 4.0

22 |
23 |
24 | 25 | Use a nickname password on IRC 26 | 27 |

On some IRC networks, you can register your nickname with a service called 28 | NickServ. By sending special messages to NickServ, you can set your password 29 | and identify yourself. Some IRC chat rooms may not allow you to join without 30 | a registered nickname.

31 | 32 |

Polari does not currently support nickname registration 33 | automatically. Each IRC network handles registering a specific nickname 34 | differently and you will need to check with the IRC network on how to 35 | register your nickname. After you have registered a nickname, you will 36 | need to message NickServ to identify yourself after connecting to the 37 | IRC Network with the following command:

38 | 39 | 40 |

/msg NickServ identify username password

41 |
42 | 43 |

Press Enter

44 |
45 |
46 | 47 |

Use the username and password you registered with the IRC network with this 48 | command. 49 |

50 |
51 | 52 |

Polari will remember your username and password and the next 53 | time you connect to the IRC network Polari will automatically 54 | register you with the NickServ service.

55 | 56 | 57 | 58 |

These instructions only allow you to use a password-protected nickname 59 | on certain IRC networks. It is not currently possible to register an IRC 60 | nickname or change your nickname password using Polari.

61 |
62 |
63 | -------------------------------------------------------------------------------- /help/C/irc-start-conversation.page: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | Start a conversation with an individual in IRC. 10 | 11 | 12 | Milo Casagrande 13 | milo@ubuntu.com 14 | 15 | 16 | Paul Cutler 17 | pcutler@gnome.org 18 | 19 | 20 |

Creative Commons Share Alike 4.0

21 |
22 |
23 | 24 | Chat with somebody on IRC 25 | 26 |

You can hold private conversations with other IRC users, outside of the 27 | public IRC chat rooms. To start a conversation with another IRC user:

28 | 29 | 30 | 31 |

Click on the name of the person in the channel you would like to start 32 | a chat with. 33 |

34 |
35 | 36 |

Information about the user, including their name and the last time they 37 | were active in the channel, will appear. Click Start Conversation to 38 | start a private conversation. 39 |

40 |
41 |
42 | 43 |

Another option to start a conversation with a user:

44 | 45 | 46 | 47 |

In the upper-right hand corner of Polari a number is 48 | displayed that shows the total number of users in the chat room. 49 | Click on the number. 50 |

51 |
52 | 53 |

Select the name of the user you want to message. Information about 54 | the user, including their name and the last time they were active in 55 | the channel, will appear.

56 |
57 | 58 |

Press Start Conversation to start a private conversation.

59 |
60 |
61 | 62 |

A new private conversation will start and a chat room will be created. 63 | The chat room will be shown in the left sidebar of Polari 64 | with the name of the person you are chatting with. 65 |

66 | 67 | 68 | 69 |
70 | -------------------------------------------------------------------------------- /help/C/legal.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 |

This work is licensed under a 5 | Creative Commons 6 | Attribution-ShareAlike 4.0 International license.

7 | 8 |

As a special exception, the copyright holders give you permission to copy, 9 | modify, and distribute the example code contained in this documentation under 10 | the terms of your choosing, without restriction.

11 | 12 |
13 | -------------------------------------------------------------------------------- /help/C/network-connect.page: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Ankit R Gadiya 12 | ankit4922@gmail.com 13 | 2016 14 | 15 | 16 | Paul Cutler 17 | pcutler@gnome.org 18 | 2017 19 | 20 | 21 | How to connect to an IRC network. 22 | 23 | 24 | Connect to an IRC Network 25 | 26 |

The first time you start Polari you will be asked to join a 27 | network. Polari comes with over fifty IRC networks configured 28 | for you. Choose the IRC Network you would like to connect to and click 29 | on it. 30 |

31 | 32 |

If you do not see the IRC network you would like to join, you can also 33 | enter the details of the IRC network. Click the Custom Network 34 | button and enter the Server Address. You can optionally 35 | enter the name of the network as you want it to appear in the list of 36 | networks in the Network Name field. 37 |

38 | 39 |

After you have connected to an IRC Network, you can also connect to 40 | other IRC Networks. 41 |

42 | 43 | 44 | 45 |

Press on the + in the top left corner.

46 |
47 | 48 |

Press Add Network and select the IRC network from the 49 | list you would like to join or press Custom Network and 50 | enter the details.

51 |
52 | 53 |

Enter the name of the chat room you would like to join or 54 | select the chat room(s) from the list and then press 55 | Join.

56 |
57 | 58 |

Press the Join button to connect to the room or rooms.

59 |
60 |
61 | 62 |

If the IRC network you are connecting to uses a custom port, 63 | you can append it to the name of the network with a 64 | colon and the port number, for example irc.example.org:12345

65 |
66 |
67 | -------------------------------------------------------------------------------- /help/C/overview.page: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 9 | 10 | What IRC is and how you can use it. 11 | 12 | 13 | Paul Cutler 14 | pcutler@gnome.org 15 | 16 | 17 |

Creative Commons Share Alike 4.0

18 |
19 |
20 | 21 | Overview of Internet Relay Chat 22 | 23 |

24 | Internet Relay Chat (IRC) is a means of chatting with people online using 25 | public and private chatrooms. IRC networks, such as Freenode or Gimpnet, 26 | are unique and host their own servers with its own channels. IRC uses 27 | a client / server model where you can use a client, such as Polari, to 28 | connect to an IRC server and join a channel. Channels, also known 29 | as chat rooms, are where users meet online to chat. 30 | 31 |

32 | 33 |
34 | -------------------------------------------------------------------------------- /help/C/sharing.page: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | How to easily share multiple lines of text or images. 10 | 11 | 12 | Paul Cutler 13 | pcutler@gnome.org 14 | 2017 15 | 16 | 17 |

Creative Commons Share Alike 4.0

18 |
19 | 20 |
21 | 22 | Share text or images 23 | 24 |

It is considered rude in IRC to copy and paste more than five lines of text. 25 | If you attempt to paste more than five lines of text, Polari will ask you if you 26 | would like the text to automatically be pasted to a public paste service. If you agree to paste 27 | the text, press the Paste button as seen in the following screenshot. After pressing 28 | Paste, the link to the paste website will be automatically entered into the chat box. 29 | Press Enter to share the link with the chat room. 30 |

31 | 32 | 33 | 34 |

To share an image file, drag and drop the image from Files into the chat box 35 | in Polari or drag and drop a file from Files to Polari. 36 | You will be asked if you wish to upload the image to a public paste service. 37 | If you agree to upload the image, press the Paste button as seen in the following screenshot. 38 | After pressing Paste, the link to the where the image was uploaded will be automatically 39 | entered into the chat box. Press Enter to share the link with the chat room. 40 |

41 | 42 |
43 | -------------------------------------------------------------------------------- /help/LINGUAS: -------------------------------------------------------------------------------- 1 | ca 2 | cs 3 | da 4 | de 5 | el 6 | es 7 | eu 8 | fr 9 | hu 10 | id 11 | nl 12 | pl 13 | pt 14 | pt_BR 15 | ru 16 | sv 17 | tr 18 | uk 19 | -------------------------------------------------------------------------------- /help/da/images/polari-paste.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GNOME/polari/70b019c47bba9b8abf77ac17b2b15b5551b8c2e9/help/da/images/polari-paste.png -------------------------------------------------------------------------------- /help/da/images/polari-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GNOME/polari/70b019c47bba9b8abf77ac17b2b15b5551b8c2e9/help/da/images/polari-screenshot.png -------------------------------------------------------------------------------- /help/de/images/polari-paste.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GNOME/polari/70b019c47bba9b8abf77ac17b2b15b5551b8c2e9/help/de/images/polari-paste.png -------------------------------------------------------------------------------- /help/de/images/polari-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GNOME/polari/70b019c47bba9b8abf77ac17b2b15b5551b8c2e9/help/de/images/polari-screenshot.png -------------------------------------------------------------------------------- /help/meson.build: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2016 Florian Müllner 2 | # 3 | # SPDX-License-Identifier: GPL-2.0-or-later 4 | 5 | gnome.yelp( 6 | meson.project_name(), 7 | sources: [ 8 | 'index.page', 9 | 'introduction.page', 10 | 'irc-commands.page', 11 | 'irc-join-room.page', 12 | 'irc-nick-password.page', 13 | 'irc-start-conversation.page', 14 | 'legal.xml', 15 | 'network-connect.page', 16 | 'overview.page', 17 | 'sharing.page', 18 | ], 19 | media: [ 20 | 'images/polari-paste.png', 21 | 'images/polari-screenshot.png', 22 | 'images/Polari.png', 23 | ], 24 | ) 25 | -------------------------------------------------------------------------------- /help/pl/images/polari-paste.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GNOME/polari/70b019c47bba9b8abf77ac17b2b15b5551b8c2e9/help/pl/images/polari-paste.png -------------------------------------------------------------------------------- /help/pl/images/polari-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GNOME/polari/70b019c47bba9b8abf77ac17b2b15b5551b8c2e9/help/pl/images/polari-screenshot.png -------------------------------------------------------------------------------- /lint/eslintrc-polari.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2018 Florian Müllner 2 | # 3 | # SPDX-License-Identifier: MIT OR LGPL-2.0-or-later 4 | 5 | rules: 6 | camelcase: 7 | - error 8 | - properties: never 9 | allow: [^vfunc_, ^on_] 10 | jsdoc/require-jsdoc: 11 | - error 12 | - publicOnly: true 13 | prefer-arrow-callback: error 14 | globals: 15 | debug: readonly 16 | info: readonly 17 | warning: readonly 18 | critical: readonly 19 | error: readonly 20 | pkg: readonly 21 | _: readonly 22 | C_: readonly 23 | N_: readonly 24 | ngettext: readonly 25 | vprintf: readonly 26 | parserOptions: 27 | sourceType: module 28 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GNOME/polari/70b019c47bba9b8abf77ac17b2b15b5551b8c2e9/logo.png -------------------------------------------------------------------------------- /logo.png.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2013 Sam Hewitt 2 | SPDX-FileCopyrightText: 2018 Tobias Bernard 3 | 4 | SPDX-License-Identifier: LGPL-2.0-or-later 5 | -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2016 Florian Müllner 2 | # 3 | # SPDX-License-Identifier: GPL-2.0-or-later 4 | 5 | project( 6 | 'polari', 7 | 'c', 8 | version: '46.0', 9 | meson_version: '>= 1.1.0', 10 | license: 'GPL-2.0-or-later', 11 | ) 12 | 13 | app_id = 'org.gnome.Polari' 14 | 15 | gnome = import('gnome') 16 | i18n = import('i18n') 17 | 18 | prefix = get_option('prefix') 19 | 20 | bindir = join_paths(prefix, get_option('bindir')) 21 | libdir = join_paths(prefix, get_option('libdir')) 22 | datadir = join_paths(prefix, get_option('datadir')) 23 | 24 | pkglibdir = join_paths(libdir, meson.project_name()) 25 | pkgdatadir = join_paths(datadir, meson.project_name()) 26 | 27 | desktopdir = join_paths(datadir, 'applications') 28 | icondir = join_paths(datadir, 'icons') 29 | metainfodir = join_paths(datadir, 'metainfo') 30 | schemadir = join_paths(datadir, 'glib-2.0', 'schemas') 31 | servicedir = join_paths(datadir, 'dbus-1', 'services') 32 | tpclientdir = join_paths(datadir, 'telepathy', 'clients') 33 | girdir = join_paths(pkgdatadir, 'gir-1.0') 34 | typelibdir = join_paths(pkglibdir, 'girepository-1.0') 35 | 36 | desktop_file_validate = find_program('desktop-file-validate', required: false) 37 | json_glib_validate = find_program('json-glib-validate', required: false) 38 | appstreamcli = find_program('appstreamcli', required: false) 39 | check_version = find_program('meson/check-version.py') 40 | 41 | gio = dependency('gio-2.0', version: '>= 2.43.4') 42 | telepathy_glib = dependency('telepathy-glib') 43 | tracker = dependency('tracker-sparql-3.0') 44 | girepository = dependency('gobject-introspection-1.0') 45 | gjs = dependency('gjs-1.0', version: '>= 1.73.1') 46 | 47 | conf = configuration_data() 48 | 49 | conf.set_quoted('PKGLIBDIR', pkglibdir) 50 | 51 | cc = meson.get_compiler('c') 52 | gjs_has_autocleanup = cc.compiles( 53 | ''' 54 | #include 55 | void main(void) { g_autoptr(GjsContext) context = NULL; } 56 | ''', 57 | dependencies: [gjs], 58 | ) 59 | 60 | conf.set('HAVE_STRCASESTR', cc.has_function('strcasestr')) 61 | conf.set('SNAPSHOT', get_option('snapshot')) 62 | conf.set('GJS_HAS_AUTOCLEANUP', gjs_has_autocleanup) 63 | 64 | config_h = declare_dependency( 65 | sources: configure_file(configuration: conf, output: 'config.h'), 66 | include_directories: include_directories('.'), 67 | ) 68 | 69 | gnome.post_install( 70 | glib_compile_schemas: true, 71 | gtk_update_icon_cache: true, 72 | update_desktop_database: true, 73 | ) 74 | 75 | subdir('data') 76 | subdir('src') 77 | subdir('po') 78 | subdir('help') 79 | 80 | meson.add_dist_script(check_version, meson.project_version(), 'NEWS') 81 | meson.add_dist_script( 82 | check_version, 83 | meson.project_version(), 84 | '--type', 'metainfo', 85 | 'data/metainfo/org.gnome.Polari.metainfo.xml.in', 86 | ) 87 | 88 | summary('prefix', get_option('prefix')) 89 | summary('bindir', get_option('bindir')) 90 | summary('libdir', get_option('libdir')) 91 | summary('datadir', get_option('datadir')) 92 | 93 | summary('buildtype', get_option('buildtype'), section: 'Build Configuration') 94 | summary('snapshot', get_option('snapshot'), section: 'Build Configuration') 95 | summary('debug', get_option('debug'), section: 'Build Configuration') 96 | -------------------------------------------------------------------------------- /meson.format: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: CC0-1.0 2 | # SPDX-FileCopyrightText: 2024 Florian Müllner 3 | 4 | use_editor_config = true 5 | group_arg_value = true 6 | -------------------------------------------------------------------------------- /meson.options: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2018 Florian Müllner 2 | # 3 | # SPDX-License-Identifier: GPL-2.0-or-later 4 | 5 | option('snapshot', 6 | type: 'boolean', 7 | value: false, 8 | description: 'Build development snapshot' 9 | ) 10 | -------------------------------------------------------------------------------- /meson/check-version.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # SPDX-FileCopyrightText: 2021 Florian Müllner 4 | # 5 | # SPDX-License-Identifier: GPL-2.0-or-later 6 | 7 | import os, sys 8 | from pathlib import Path 9 | from xml.etree.ElementTree import ElementTree 10 | import argparse 11 | 12 | def check_version(version, file, type='news'): 13 | if type == 'news': 14 | line = file.open().readline() 15 | ok = line.startswith(version) 16 | print("{}: {}".format(file, "OK" if ok else "FAILED")) 17 | if not ok: 18 | raise Exception("{} does not start with {}".format(file, version)) 19 | elif type == 'metainfo': 20 | query = './releases/release[@version="{}"]'.format(version) 21 | ok = ElementTree(file=file).find(query) is not None 22 | print("{}: {}".format(file, "OK" if ok else "FAILED")) 23 | if not ok: 24 | raise Exception("{} does not contain release {}".format(file, version)) 25 | else: 26 | raise Exception('Not implemented') 27 | 28 | parser = argparse.ArgumentParser(description='Check release version information.') 29 | parser.add_argument('--type', choices=['metainfo','news'], default='news') 30 | parser.add_argument('version', help='the version to check for') 31 | parser.add_argument('files', nargs='+', help='files to check') 32 | args = parser.parse_args() 33 | 34 | distroot = os.environ.get('MESON_DIST_ROOT', './') 35 | 36 | try: 37 | for file in args.files: 38 | check_version(args.version, Path(distroot, file), args.type) 39 | except: 40 | sys.exit(1) 41 | -------------------------------------------------------------------------------- /po/LINGUAS: -------------------------------------------------------------------------------- 1 | af 2 | ar 3 | as 4 | be 5 | bg 6 | bs 7 | ca 8 | ca@valencia 9 | ckb 10 | cs 11 | da 12 | de 13 | el 14 | en_GB 15 | eo 16 | es 17 | et 18 | eu 19 | fa 20 | fi 21 | fr 22 | fur 23 | gl 24 | he 25 | hi 26 | hr 27 | hu 28 | id 29 | is 30 | it 31 | ja 32 | ka 33 | kk 34 | ko 35 | lo 36 | lv 37 | lt 38 | ml 39 | ms 40 | nb 41 | ne 42 | nl 43 | oc 44 | pa 45 | pl 46 | pt 47 | pt_BR 48 | ro 49 | ru 50 | sk 51 | sl 52 | sr 53 | sr@latin 54 | sv 55 | te 56 | tg 57 | th 58 | tr 59 | uk 60 | vi 61 | zh_CN 62 | zh_HK 63 | zh_TW 64 | -------------------------------------------------------------------------------- /po/POTFILES.in: -------------------------------------------------------------------------------- 1 | # List of source files containing translatable strings. 2 | # Please keep this file sorted alphabetically. 3 | data/metainfo/org.gnome.Polari.metainfo.xml.in 4 | data/org.gnome.Polari.desktop.in 5 | data/org.gnome.Polari.gschema.xml 6 | data/resources/connection-details.ui 7 | data/resources/connection-properties.ui 8 | data/resources/entry-area.ui 9 | data/resources/help-overlay.ui 10 | data/resources/initial-setup-window.ui 11 | data/resources/join-room-dialog.ui 12 | data/resources/main-window.ui 13 | data/resources/nick-popover.ui 14 | data/resources/room-list-header.ui 15 | data/resources/server-room-list.ui 16 | data/resources/user-details.ui 17 | src/application.js 18 | src/chatView.js 19 | src/connections.js 20 | src/entryArea.js 21 | src/initialSetup.js 22 | src/ircParser.js 23 | src/joinDialog.js 24 | src/mainWindow.js 25 | src/pasteManager.js 26 | src/roomList.js 27 | src/roomStack.js 28 | src/telepathyClient.js 29 | src/userList.js 30 | src/userTracker.js 31 | src/utils.js 32 | -------------------------------------------------------------------------------- /po/meson.build: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2016 Florian Müllner 2 | # 3 | # SPDX-License-Identifier: GPL-2.0-or-later 4 | 5 | i18n.gettext(meson.project_name(), preset: 'glib') 6 | -------------------------------------------------------------------------------- /po/zh_HK.po: -------------------------------------------------------------------------------- 1 | # Chinese (Hong Kong) translation for polari. 2 | # Copyright (C) 2013 polari's COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the polari package. 4 | # Cheng-Chia Tseng , 2013. 5 | # 6 | msgid "" 7 | msgstr "" 8 | "Project-Id-Version: polari master\n" 9 | "Report-Msgid-Bugs-To: \n" 10 | "POT-Creation-Date: 2013-10-10 08:21+0800\n" 11 | "PO-Revision-Date: 2013-10-10 08:21+0800\n" 12 | "Last-Translator: Cheng-Chia Tseng \n" 13 | "Language-Team: Chinese (Hong Kong) \n" 14 | "Language: zh_HK\n" 15 | "MIME-Version: 1.0\n" 16 | "Content-Type: text/plain; charset=UTF-8\n" 17 | "Content-Transfer-Encoding: 8bit\n" 18 | "X-Generator: Poedit 1.5.4\n" 19 | 20 | #: ../data/org.gnome.polari.gschema.xml.h:1 21 | msgid "Saved channel list" 22 | msgstr "儲存的頻道清單" 23 | 24 | #: ../data/org.gnome.polari.gschema.xml.h:2 25 | msgid "List of channels to restore on startup" 26 | msgstr "啟動時要還原的頻道清單" 27 | 28 | #: ../data/org.gnome.polari.gschema.xml.h:3 29 | msgid "Window size" 30 | msgstr "視窗大小" 31 | 32 | #: ../data/org.gnome.polari.gschema.xml.h:4 33 | msgid "Window size (width and height)." 34 | msgstr "視窗大小 (闊度和高度)。" 35 | 36 | #: ../data/org.gnome.polari.gschema.xml.h:5 37 | msgid "Window position" 38 | msgstr "視窗位置" 39 | 40 | #: ../data/org.gnome.polari.gschema.xml.h:6 41 | msgid "Window position (x and y)." 42 | msgstr "視窗位置 (x 與 y)。" 43 | 44 | #: ../data/org.gnome.polari.gschema.xml.h:7 45 | msgid "Window maximized" 46 | msgstr "視窗最大化" 47 | 48 | #: ../data/org.gnome.polari.gschema.xml.h:8 49 | msgid "Window maximized state" 50 | msgstr "視窗最大化狀態" 51 | 52 | #: ../data/org.gnome.Polari.desktop.in.h:1 53 | #: ../data/resources/main-window.ui.h:3 54 | msgid "Polari" 55 | msgstr "Polari" 56 | 57 | #: ../data/org.gnome.Polari.desktop.in.h:2 ../src/application.js:405 58 | msgid "An Internet Relay Chat Client for GNOME" 59 | msgstr "GNOME 的互聯網中繼聊天客戶端" 60 | 61 | #: ../data/org.gnome.Polari.desktop.in.h:3 62 | msgid "IRC;Internet;Relay;Chat;" 63 | msgstr "IRC;Internet;Relay;Chat;互聯網;中繼;轉繼;聊天;清談;" 64 | 65 | #: ../data/resources/app-menu.ui.h:1 66 | #: ../data/resources/connection-list-dialog.ui.h:1 67 | msgid "Connections" 68 | msgstr "連線" 69 | 70 | #: ../data/resources/app-menu.ui.h:2 71 | msgid "Preferences" 72 | msgstr "偏好設定" 73 | 74 | #: ../data/resources/app-menu.ui.h:3 75 | msgid "About Polari" 76 | msgstr "關於 Polari" 77 | 78 | #: ../data/resources/app-menu.ui.h:4 79 | msgid "Quit" 80 | msgstr "退出" 81 | 82 | #: ../data/resources/connection-details-dialog.ui.h:1 83 | msgid "New Connection" 84 | msgstr "新增連線" 85 | 86 | #: ../data/resources/connection-details-dialog.ui.h:2 87 | #: ../data/resources/join-room-dialog.ui.h:2 88 | msgid "_Cancel" 89 | msgstr "取消(_C)" 90 | 91 | #: ../data/resources/connection-details-dialog.ui.h:3 92 | msgid "Cr_eate" 93 | msgstr "建立(_E)" 94 | 95 | #: ../data/resources/connection-details-dialog.ui.h:4 96 | #: ../data/resources/join-room-dialog.ui.h:5 97 | msgid "Server" 98 | msgstr "伺服器" 99 | 100 | #: ../data/resources/connection-details-dialog.ui.h:5 101 | msgid "_Address" 102 | msgstr "位址(_A) " 103 | 104 | #: ../data/resources/connection-details-dialog.ui.h:6 105 | msgid "_Description" 106 | msgstr "描述(_D)" 107 | 108 | #: ../data/resources/connection-details-dialog.ui.h:7 109 | msgid "Identity" 110 | msgstr "身份" 111 | 112 | #: ../data/resources/connection-details-dialog.ui.h:8 113 | msgid "_Nickname" 114 | msgstr "網名(_N)" 115 | 116 | #: ../data/resources/connection-details-dialog.ui.h:9 117 | msgid "_Real Name" 118 | msgstr "實名(_R)" 119 | 120 | #: ../data/resources/connection-details-dialog.ui.h:10 121 | #: ../data/resources/join-room-dialog.ui.h:4 122 | msgid "optional" 123 | msgstr "選填" 124 | 125 | #: ../data/resources/connection-list-dialog.ui.h:2 126 | msgid "_Close" 127 | msgstr "關閉(_C)" 128 | 129 | #: ../data/resources/connection-list-dialog.ui.h:3 130 | msgid "Add" 131 | msgstr "加入" 132 | 133 | #: ../data/resources/connection-list-dialog.ui.h:4 134 | msgid "Remove" 135 | msgstr "移除" 136 | 137 | #: ../data/resources/join-room-dialog.ui.h:1 138 | msgid "Join Chat Room" 139 | msgstr "參與聊天室" 140 | 141 | #: ../data/resources/join-room-dialog.ui.h:3 142 | msgid "_Join" 143 | msgstr "參與(_J)" 144 | 145 | #: ../data/resources/join-room-dialog.ui.h:6 146 | msgid "Room" 147 | msgstr "聊天室" 148 | 149 | #: ../data/resources/join-room-dialog.ui.h:7 150 | msgid "C_onnection" 151 | msgstr "連線(_O)" 152 | 153 | #: ../data/resources/join-room-dialog.ui.h:8 154 | msgid "_Name" 155 | msgstr "名稱(_N)" 156 | 157 | #: ../data/resources/join-room-dialog.ui.h:9 158 | msgid "_Password" 159 | msgstr "密碼(_P)" 160 | 161 | #: ../data/resources/main-window.ui.h:1 162 | msgid "Join a Room" 163 | msgstr "參與一間聊天室" 164 | 165 | #: ../data/resources/main-window.ui.h:2 166 | msgid "Message a User" 167 | msgstr "傳訊給一位使用者" 168 | 169 | #: ../data/resources/main-window.ui.h:4 170 | msgid "_Leave" 171 | msgstr "離開(_L)" 172 | 173 | #: ../src/application.js:354 174 | msgid "Good Bye" 175 | msgstr "再見" 176 | 177 | #: ../src/application.js:404 178 | msgid "translator-credits" 179 | msgstr "Cheng-Chia Tseng , 2013." 180 | 181 | #: ../src/chatView.js:345 182 | #, c-format 183 | msgid "%s is now known as %s" 184 | msgstr "%s 現在改以 %s 為名" 185 | 186 | #: ../src/chatView.js:350 187 | #, c-format 188 | msgid "%s has disconnected" 189 | msgstr "%s 已失去連線" 190 | 191 | #: ../src/chatView.js:358 192 | #, c-format 193 | msgid "%s has been kicked by %s" 194 | msgstr "%s 已被 %s 踢出" 195 | 196 | #: ../src/chatView.js:360 197 | #, c-format 198 | msgid "%s has been kicked" 199 | msgstr "%s 已被踢出" 200 | 201 | #: ../src/chatView.js:366 202 | #, c-format 203 | msgid "%s has been banned by %s" 204 | msgstr "%s 已被 %s 禁言" 205 | 206 | #: ../src/chatView.js:368 207 | #, c-format 208 | msgid "%s has been banned" 209 | msgstr "%s 已被禁言" 210 | 211 | #: ../src/chatView.js:373 212 | #, c-format 213 | msgid "%s joined" 214 | msgstr "%s 已參與" 215 | 216 | #: ../src/chatView.js:377 217 | #, c-format 218 | msgid "%s left" 219 | msgstr "%s 已離開" 220 | 221 | #: ../src/connections.js:228 222 | msgid "Edit Connection" 223 | msgstr "編輯連線" 224 | 225 | #: ../src/connections.js:229 226 | msgid "A_pply" 227 | msgstr "套用(_P)" 228 | 229 | #: ../src/userList.js:101 230 | msgid "All" 231 | msgstr "全部" 232 | -------------------------------------------------------------------------------- /polari.doap: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 13 | Polari 14 | An IRC Client for GNOME 15 | Polari is a simple IRC Client that is designed to integrate seamlessly with GNOME 16 | 17 | 18 | 19 | 20 | JavaScript 21 | C 22 | 23 | 24 | 25 | Florian Müllner 26 | 27 | fmuellner 28 | 29 | 30 | 31 | 32 | Marge Bot 33 | marge-bot 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /src/config.js.in: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 Florian Müllner 2 | // 3 | // SPDX-License-Identifier: GPL-2.0-or-later 4 | export const PACKAGE_NAME = '@PACKAGE_NAME@'; 5 | export const PACKAGE_VERSION = '@PACKAGE_VERSION@'; 6 | export const PREFIX = '@PREFIX@'; 7 | export const LIBDIR = '@LIBDIR@'; 8 | -------------------------------------------------------------------------------- /src/initialSetup.js: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2016 Danny Mølgaard 2 | // SPDX-FileCopyrightText: 2017 Florian Müllner 3 | // 4 | // SPDX-License-Identifier: GPL-2.0-or-later 5 | 6 | import Gio from 'gi://Gio'; 7 | import GLib from 'gi://GLib'; 8 | import GObject from 'gi://GObject'; 9 | import Gtk from 'gi://Gtk'; 10 | import Tp from 'gi://TelepathyGLib'; 11 | 12 | Gio._promisify(Tp.Account.prototype, 'remove_async', 'remove_finish'); 13 | 14 | const SetupPage = { 15 | CONNECTION: 0, 16 | ROOM: 1, 17 | OFFLINE: 2, 18 | }; 19 | 20 | export default GObject.registerClass( 21 | class InitialSetupWindow extends Gtk.Window { 22 | static [Gtk.template] = 'resource:///org/gnome/Polari/ui/initial-setup-window.ui'; 23 | static [Gtk.internalChildren] = [ 24 | 'contentStack', 25 | 'connectionsList', 26 | 'nextButton', 27 | 'prevButton', 28 | 'serverRoomList', 29 | ]; 30 | 31 | constructor(params) { 32 | super(params); 33 | 34 | this._currentAccount = null; 35 | 36 | this._connectionsList.connect('account-created', (w, account) => { 37 | this._setPage(SetupPage.ROOM); 38 | this._currentAccount = account; 39 | this._serverRoomList.setAccount(account); 40 | }); 41 | 42 | this.connect('destroy', () => this._unsetAccount()); 43 | 44 | this._serverRoomList.connect('notify::can-join', 45 | this._updateNextSensitivity.bind(this)); 46 | 47 | this._nextButton.connect('clicked', () => { 48 | if (this._page === SetupPage.CONNECTION) { 49 | this._connectionsList.activateSelected(); 50 | } else { 51 | this._joinRooms(); 52 | this._currentAccount = null; 53 | this.destroy(); 54 | } 55 | }); 56 | 57 | this._prevButton.connect('clicked', () => { 58 | if (this._page === SetupPage.ROOM) { 59 | this._setPage(SetupPage.CONNECTION); 60 | this._unsetAccount(); 61 | } else { 62 | this.destroy(); 63 | } 64 | }); 65 | 66 | this._networkMonitor = Gio.NetworkMonitor.get_default(); 67 | this._networkMonitor.connect('notify::network-available', 68 | this._onNetworkAvailableChanged.bind(this)); 69 | if (this._networkMonitor.state_valid) 70 | this._onNetworkAvailableChanged(); 71 | } 72 | 73 | _onNetworkAvailableChanged() { 74 | if (this._networkMonitor.network_available) { 75 | this._setPage(this._currentAccount 76 | ? SetupPage.ROOM : SetupPage.CONNECTION); 77 | } else { 78 | this._setPage(SetupPage.OFFLINE); 79 | } 80 | } 81 | 82 | _setPage(page) { 83 | if (page === SetupPage.CONNECTION) 84 | this._contentStack.visible_child_name = 'connections'; 85 | else if (page === SetupPage.ROOM) 86 | this._contentStack.visible_child_name = 'rooms'; 87 | else 88 | this._contentStack.visible_child_name = 'offline-hint'; 89 | 90 | let isLastPage = page === SetupPage.ROOM; 91 | 92 | this._prevButton.label = isLastPage ? _('_Back') : _('_Cancel'); 93 | this._nextButton.label = isLastPage ? _('_Done') : _('_Next'); 94 | 95 | if (isLastPage) 96 | this._nextButton.add_css_class('suggested-action'); 97 | else 98 | this._nextButton.remove_css_class('suggested-action'); 99 | 100 | this._updateNextSensitivity(); 101 | } 102 | 103 | async _unsetAccount() { 104 | if (!this._currentAccount) 105 | return; 106 | 107 | await this._currentAccount.remove_async(); 108 | this._currentAccount = null; 109 | } 110 | 111 | get _page() { 112 | if (this._contentStack.visible_child_name === 'rooms') 113 | return SetupPage.ROOM; 114 | else if (this._contentStack.visible_child_name === 'connections') 115 | return SetupPage.CONNECTION; 116 | else 117 | return SetupPage.OFFLINE; 118 | } 119 | 120 | _updateNextSensitivity() { 121 | let sensitive = this._page !== SetupPage.OFFLINE; 122 | 123 | if (this._page === SetupPage.ROOM) 124 | sensitive = this._serverRoomList.can_join; 125 | 126 | this._nextButton.sensitive = sensitive; 127 | } 128 | 129 | _joinRooms() { 130 | this.hide(); 131 | 132 | let toJoinRooms = this._serverRoomList.selectedRooms; 133 | 134 | let accountPath = this._currentAccount.get_object_path(); 135 | toJoinRooms.forEach(room => { 136 | if (room[0] !== '#') 137 | room = `#${room}`; 138 | 139 | let app = Gio.Application.get_default(); 140 | let action = app.lookup_action('join-room'); 141 | action.activate(GLib.Variant.new('(ssb)', [accountPath, room, true])); 142 | }); 143 | } 144 | }); 145 | -------------------------------------------------------------------------------- /src/joinDialog.js: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2013 Florian Müllner 2 | // SPDX-FileCopyrightText: 2016 Kunaal Jain 3 | // SPDX-FileCopyrightText: 2016 Isabella Ribeiro 4 | // 5 | // SPDX-License-Identifier: GPL-2.0-or-later 6 | 7 | import Adw from 'gi://Adw'; 8 | import Gio from 'gi://Gio'; 9 | import GLib from 'gi://GLib'; 10 | import GObject from 'gi://GObject'; 11 | import Gtk from 'gi://Gtk'; 12 | 13 | import AccountsMonitor from './accountsMonitor.js'; 14 | 15 | export default GObject.registerClass( 16 | class JoinDialog extends Adw.Dialog { 17 | static [Gtk.template] = 'resource:///org/gnome/Polari/ui/join-room-dialog.ui'; 18 | static [Gtk.internalChildren] = [ 19 | 'joinButton', 20 | 'navView', 21 | 'mainPage', 22 | 'connectionPage', 23 | 'customPage', 24 | 'connectionCombo', 25 | 'filterEntry', 26 | 'connectionsList', 27 | 'serverRoomList', 28 | 'details', 29 | 'addButton', 30 | ]; 31 | 32 | constructor(params) { 33 | super(params); 34 | 35 | this._setupMainPage(); 36 | this._setupConnectionPage(); 37 | 38 | this._accountsMonitor = AccountsMonitor.getDefault(); 39 | 40 | this._accounts = new Map(); 41 | this._accountsMonitor.visibleAccounts.forEach(a => { 42 | this._accounts.set(a.display_name, a); 43 | }); 44 | this._accountAddedId = 45 | this._accountsMonitor.connect('account-added', (am, account) => { 46 | this._accounts.set(account.display_name, account); 47 | this._onAccountsChanged(); 48 | }); 49 | this._accountRemovedId = 50 | this._accountsMonitor.connect('account-removed', (am, account) => { 51 | this._accounts.delete(account.display_name); 52 | this._onAccountsChanged(); 53 | }); 54 | 55 | this._joinButton.connect('clicked', 56 | () => this._joinRoom()); 57 | 58 | this.connect('destroy', () => { 59 | this._accountsMonitor.disconnect(this._accountAddedId); 60 | this._accountsMonitor.disconnect(this._accountRemovedId); 61 | }); 62 | 63 | if (!this._hasAccounts) 64 | this._navView.push(this._connectionPage); 65 | 66 | this._navView.connect('notify::visible-page', 67 | () => this._onPageChanged()); 68 | 69 | this._onAccountsChanged(); 70 | this._onPageChanged(); 71 | 72 | const app = Gio.Application.get_default(); 73 | const action = app.lookup_action('show-join-dialog'); 74 | 75 | // disable while showing 76 | this.connect('map', 77 | () => (action.enabled = false)); 78 | this.connect('unmap', 79 | () => (action.enabled = true)); 80 | } 81 | 82 | get _hasAccounts() { 83 | return this._accounts.size > 0; 84 | } 85 | 86 | _setupMainPage() { 87 | this._connectionCombo.connect('changed', 88 | this._onAccountChanged.bind(this)); 89 | this._connectionCombo.sensitive = false; 90 | 91 | this._serverRoomList.connect('notify::can-join', 92 | this._updateCanJoin.bind(this)); 93 | } 94 | 95 | _setupConnectionPage() { 96 | this._connectionsList.connect('account-selected', () => { 97 | this._navView.pop_to_page(this._mainPage); 98 | }); 99 | this._addButton.connect('clicked', () => { 100 | this._details.save(); 101 | this._navView.pop_to_page(this._mainPage); 102 | }); 103 | 104 | this._connectionsList.connect('account-created', 105 | this._onAccountCreated.bind(this)); 106 | this._details.connect('account-created', 107 | this._onAccountCreated.bind(this)); 108 | 109 | this._filterEntry.connect('search-changed', () => { 110 | this._connectionsList.setFilter(this._filterEntry.text); 111 | }); 112 | this._filterEntry.connect('stop-search', () => { 113 | if (this._filterEntry.text.length > 0) 114 | this._filterEntry.text = ''; 115 | else 116 | this._navView.pop(); 117 | }); 118 | this._filterEntry.connect('activate', () => { 119 | if (this._filterEntry.text.length > 0) 120 | this._connectionsList.activateSelected(); 121 | }); 122 | } 123 | 124 | _onAccountChanged() { 125 | let selected = this._connectionCombo.get_active_text(); 126 | let account = this._accounts.get(selected); 127 | if (!account) 128 | return; 129 | 130 | this._serverRoomList.setAccount(account); 131 | } 132 | 133 | _onAccountCreated(w, account) { 134 | this._connectionCombo.set_active_id(account.display_name); 135 | } 136 | 137 | _joinRoom() { 138 | this.hide(); 139 | 140 | let selected = this._connectionCombo.get_active_text(); 141 | let account = this._accounts.get(selected); 142 | 143 | let toJoinRooms = this._serverRoomList.selectedRooms; 144 | toJoinRooms.forEach(room => { 145 | if (room[0] !== '#') 146 | room = `#${room}`; 147 | 148 | let app = Gio.Application.get_default(); 149 | let action = app.lookup_action('join-room'); 150 | action.activate(GLib.Variant.new('(ssb)', [ 151 | account.get_object_path(), 152 | room, 153 | true, 154 | ])); 155 | }); 156 | 157 | this.close(); 158 | } 159 | 160 | _onAccountsChanged() { 161 | this._connectionCombo.remove_all(); 162 | 163 | let names = [...this._accounts.keys()].sort((a, b) => { 164 | return a.localeCompare(b); 165 | }); 166 | for (let i = 0; i < names.length; i++) 167 | this._connectionCombo.append(names[i], names[i]); 168 | this._connectionCombo.sensitive = names.length > 1; 169 | 170 | let activeRoom = this.transient_for 171 | ? this.transient_for.active_room : null; 172 | let activeIndex = 0; 173 | if (activeRoom) 174 | activeIndex = Math.max(names.indexOf(activeRoom.account.display_name), 0); 175 | this._connectionCombo.set_active(activeIndex); 176 | 177 | this._connectionPage.set_can_pop(this._hasAccounts); 178 | } 179 | 180 | _updateCanJoin() { 181 | let sensitive = false; 182 | 183 | if (this._navView.visible_page === this._mainPage) { 184 | sensitive = this._connectionCombo.get_active() > -1 && 185 | this._serverRoomList.can_join; 186 | } 187 | 188 | this._joinButton.sensitive = sensitive; 189 | } 190 | 191 | _onPageChanged() { 192 | if (this._navView.visible_page === this._mainPage) { 193 | this._serverRoomList.focusEntry(); 194 | } else if (this._navView.visible_page === this._customPage) { 195 | this.set_default_widget(this._addButton); 196 | this._details.reset(); 197 | } 198 | this._updateCanJoin(); 199 | } 200 | }); 201 | -------------------------------------------------------------------------------- /src/lib/polari-client-factory.c: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2017 Florian Müllner 3 | * 4 | * SPDX-License-Identifier: GPL-2.0-or-later 5 | */ 6 | 7 | #include "polari-client-factory.h" 8 | 9 | G_DEFINE_TYPE (PolariClientFactory, polari_client_factory, TP_TYPE_AUTOMATIC_CLIENT_FACTORY) 10 | 11 | PolariClientFactory * 12 | polari_client_factory_new (void) 13 | { 14 | return g_object_new (POLARI_TYPE_CLIENT_FACTORY, NULL); 15 | } 16 | 17 | /** 18 | * polari_client_factory_create_account: 19 | * Returns: (transfer full): 20 | */ 21 | TpAccount * 22 | polari_client_factory_create_account (PolariClientFactory *self, 23 | const char *object_path, 24 | GError **error) 25 | { 26 | PolariClientFactoryClass *klass = POLARI_CLIENT_FACTORY_GET_CLASS (self); 27 | TpSimpleClientFactoryClass *simple_class = 28 | TP_SIMPLE_CLIENT_FACTORY_CLASS (polari_client_factory_parent_class); 29 | 30 | if (klass->create_account) 31 | return klass->create_account (self, object_path, error); 32 | 33 | return simple_class->create_account (TP_SIMPLE_CLIENT_FACTORY (self), 34 | object_path, 35 | NULL, 36 | error); 37 | } 38 | 39 | static TpAccount * 40 | polari_client_factory_create_account_impl (TpSimpleClientFactory *self, 41 | const char *object_path, 42 | const GHashTable *immutable_props G_GNUC_UNUSED, 43 | GError **error) 44 | { 45 | return polari_client_factory_create_account (POLARI_CLIENT_FACTORY (self), 46 | object_path, 47 | error); 48 | } 49 | 50 | static void 51 | polari_client_factory_class_init (PolariClientFactoryClass *klass) 52 | { 53 | TpSimpleClientFactoryClass *simple_class = TP_SIMPLE_CLIENT_FACTORY_CLASS (klass); 54 | 55 | simple_class->create_account = polari_client_factory_create_account_impl; 56 | } 57 | 58 | static void 59 | polari_client_factory_init (PolariClientFactory *self G_GNUC_UNUSED) 60 | { 61 | } 62 | -------------------------------------------------------------------------------- /src/lib/polari-client-factory.h: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2017 Florian Müllner 3 | * 4 | * SPDX-License-Identifier: GPL-2.0-or-later 5 | */ 6 | 7 | #pragma once 8 | 9 | #include 10 | 11 | G_GNUC_BEGIN_IGNORE_DEPRECATIONS 12 | #include 13 | G_GNUC_END_IGNORE_DEPRECATIONS 14 | #include "polari-tp-autocleanup.h" 15 | 16 | G_BEGIN_DECLS 17 | 18 | #define POLARI_TYPE_CLIENT_FACTORY (polari_client_factory_get_type()) 19 | 20 | G_DECLARE_DERIVABLE_TYPE (PolariClientFactory, polari_client_factory, POLARI, CLIENT_FACTORY, TpAutomaticClientFactory) 21 | 22 | struct _PolariClientFactoryClass 23 | { 24 | TpAutomaticClientFactoryClass parent; 25 | 26 | TpAccount * (*create_account) (PolariClientFactory *self, 27 | const char *object_path, 28 | GError **error); 29 | }; 30 | 31 | PolariClientFactory *polari_client_factory_new (void); 32 | TpAccount *polari_client_factory_create_account (PolariClientFactory *self, 33 | const char *object_path, 34 | GError **error); 35 | 36 | G_END_DECLS 37 | -------------------------------------------------------------------------------- /src/lib/polari-message-private.h: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2017 Florian Müllner 3 | * 4 | * SPDX-License-Identifier: GPL-2.0-or-later 5 | */ 6 | 7 | #pragma once 8 | 9 | #include "polari-message.h" 10 | 11 | G_BEGIN_DECLS 12 | 13 | struct _PolariMessage 14 | { 15 | GDateTime *time; 16 | char *sender; 17 | char *text; 18 | gboolean is_action; 19 | gboolean is_self; 20 | }; 21 | 22 | PolariMessage *polari_message_new_empty (void); 23 | 24 | G_END_DECLS 25 | -------------------------------------------------------------------------------- /src/lib/polari-message.c: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2017 Florian Müllner 3 | * 4 | * SPDX-License-Identifier: GPL-2.0-or-later 5 | */ 6 | 7 | #include "polari-message-private.h" 8 | 9 | G_DEFINE_BOXED_TYPE (PolariMessage, polari_message, polari_message_copy, polari_message_free) 10 | 11 | PolariMessage * 12 | polari_message_new_empty () 13 | { 14 | return g_new0 (PolariMessage, 1); 15 | } 16 | 17 | PolariMessage * 18 | polari_message_new (const char *text, 19 | const char *sender, 20 | GDateTime *time, 21 | gboolean is_action, 22 | gboolean is_self) 23 | { 24 | PolariMessage *self; 25 | 26 | self = polari_message_new_empty (); 27 | 28 | self->text = g_strdup (text); 29 | self->sender = g_strdup (sender); 30 | self->time = g_date_time_ref (time); 31 | self->is_action = is_action; 32 | self->is_self = is_self; 33 | 34 | return self; 35 | } 36 | 37 | PolariMessage * 38 | polari_message_new_from_tp_message (TpMessage *tp_message) 39 | { 40 | PolariMessage *self; 41 | char *text = tp_message_to_text (tp_message, NULL); 42 | TpContact *sender = tp_signalled_message_get_sender (tp_message); 43 | TpChannelTextMessageType type = tp_message_get_message_type (tp_message); 44 | gint64 timestamp; 45 | gboolean incoming; 46 | 47 | timestamp = tp_message_get_sent_timestamp (tp_message); 48 | if (timestamp == 0) 49 | timestamp = tp_message_get_received_timestamp (tp_message); 50 | 51 | tp_message_get_pending_message_id (tp_message, &incoming); 52 | 53 | self = polari_message_new (text, 54 | tp_contact_get_alias (sender), 55 | g_date_time_new_from_unix_utc (timestamp), 56 | type == TP_CHANNEL_TEXT_MESSAGE_TYPE_ACTION, 57 | !incoming); 58 | g_free (text); 59 | 60 | return self; 61 | } 62 | 63 | PolariMessage * 64 | polari_message_copy (PolariMessage *self) 65 | { 66 | g_return_val_if_fail (self, NULL); 67 | 68 | return polari_message_new (self->text, 69 | self->sender, 70 | self->time, 71 | self->is_action, 72 | self->is_self); 73 | } 74 | 75 | void 76 | polari_message_free (PolariMessage *self) 77 | { 78 | g_return_if_fail (self); 79 | 80 | g_free (self->text); 81 | g_free (self->sender); 82 | g_date_time_unref (self->time); 83 | 84 | g_free (self); 85 | } 86 | 87 | const char * 88 | polari_message_get_text (PolariMessage *message) 89 | { 90 | return message->text; 91 | } 92 | 93 | const char * 94 | polari_message_get_sender (PolariMessage *message) 95 | { 96 | return message->sender; 97 | } 98 | 99 | /** 100 | * polari_message_get_time: 101 | * 102 | * Returns: (transfer none): 103 | **/ 104 | GDateTime * 105 | polari_message_get_time (PolariMessage *message) 106 | { 107 | return message->time; 108 | } 109 | 110 | gboolean 111 | polari_message_is_action (PolariMessage *message) 112 | { 113 | return message->is_action; 114 | } 115 | 116 | gboolean 117 | polari_message_is_self (PolariMessage *message) 118 | { 119 | return message->is_self; 120 | } 121 | 122 | static TrackerResource * 123 | create_account_resource (const char *id) 124 | { 125 | TrackerResource *res; 126 | g_autofree char *uri = NULL; 127 | 128 | uri = g_strconcat ("urn:account:", id, NULL); 129 | 130 | res = tracker_resource_new (uri); 131 | 132 | tracker_resource_set_uri (res, "rdf:type", "polari:Account"); 133 | tracker_resource_set_string (res, "polari:id", id); 134 | 135 | return res; 136 | } 137 | 138 | static TrackerResource * 139 | create_channel_resource (const char *name, 140 | const char *account_id, 141 | gboolean is_room) 142 | { 143 | TrackerResource *res; 144 | g_autofree char *uri = NULL; 145 | 146 | uri = g_strdup_printf ("urn:channel:%s:%s", account_id, name); 147 | 148 | res = tracker_resource_new (uri); 149 | 150 | tracker_resource_set_uri (res, "rdf:type", is_room ? "polari:Room" 151 | : "polari:Conversation"); 152 | tracker_resource_set_string (res, "polari:name", name); 153 | tracker_resource_set_take_relation (res, "polari:account", 154 | create_account_resource (account_id)); 155 | 156 | return res; 157 | } 158 | 159 | static TrackerResource * 160 | create_sender_resource (const char *nick, 161 | const char *account_id, 162 | gboolean is_self) 163 | { 164 | TrackerResource *res; 165 | g_autofree char *uri = NULL, *id = NULL; 166 | 167 | id = g_ascii_strdown (nick, -1); 168 | uri = g_strdup_printf ("urn:contact:%s:%s", account_id, id); 169 | 170 | res = tracker_resource_new (uri); 171 | 172 | tracker_resource_set_uri (res, "rdf:type", is_self ? "polari:SelfContact" 173 | : "polari:Contact"); 174 | tracker_resource_set_string (res, "polari:nick", nick); 175 | 176 | return res; 177 | } 178 | 179 | /** 180 | * polari_message_to_tracker_resource: 181 | * 182 | * Returns: (transfer full): 183 | */ 184 | TrackerResource * 185 | polari_message_to_tracker_resource (PolariMessage *message, 186 | const char *account_id, 187 | const char *channel_name, 188 | gboolean is_room) 189 | { 190 | TrackerResource *res, *rel; 191 | GDateTime *time; 192 | 193 | res = tracker_resource_new (NULL); 194 | 195 | tracker_resource_set_uri (res, "rdf:type", "polari:Message"); 196 | 197 | if (polari_message_is_action (message)) 198 | tracker_resource_set_boolean (res, "polari:isAction", TRUE); 199 | 200 | time = polari_message_get_time (message); 201 | tracker_resource_set_datetime (res, "polari:time", time); 202 | 203 | tracker_resource_set_string (res, "polari:text", polari_message_get_text (message)); 204 | 205 | rel = create_sender_resource (polari_message_get_sender (message), 206 | account_id, 207 | polari_message_is_self (message)); 208 | tracker_resource_set_take_relation (res, "polari:sender", rel); 209 | 210 | rel = create_channel_resource (channel_name, account_id, is_room); 211 | tracker_resource_set_take_relation (res, "polari:channel", rel); 212 | 213 | return res; 214 | } 215 | -------------------------------------------------------------------------------- /src/lib/polari-message.h: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2017 Florian Müllner 3 | * 4 | * SPDX-License-Identifier: GPL-2.0-or-later 5 | */ 6 | 7 | #pragma once 8 | 9 | #include 10 | G_GNUC_BEGIN_IGNORE_DEPRECATIONS 11 | #include 12 | G_GNUC_END_IGNORE_DEPRECATIONS 13 | 14 | G_BEGIN_DECLS 15 | 16 | GType polari_message_get_type (void) G_GNUC_CONST; 17 | #define POLARI_TYPE_MESSAGE (polari_message_get_type()) 18 | 19 | typedef struct _PolariMessage PolariMessage; 20 | 21 | PolariMessage *polari_message_new (const char *text, 22 | const char *sender, 23 | GDateTime *time, 24 | gboolean is_action, 25 | gboolean is_self); 26 | PolariMessage *polari_message_new_from_tp_message (TpMessage *tp_message); 27 | 28 | PolariMessage *polari_message_copy (PolariMessage *self); 29 | void polari_message_free (PolariMessage *self); 30 | 31 | const char *polari_message_get_text (PolariMessage *message); 32 | const char *polari_message_get_sender (PolariMessage *message); 33 | GDateTime *polari_message_get_time (PolariMessage *message); 34 | gboolean polari_message_is_action (PolariMessage *message); 35 | gboolean polari_message_is_self (PolariMessage *message); 36 | 37 | TrackerResource *polari_message_to_tracker_resource (PolariMessage *message, 38 | const char *account_id, 39 | const char *channel_name, 40 | gboolean is_room); 41 | 42 | G_DEFINE_AUTOPTR_CLEANUP_FUNC (PolariMessage, polari_message_free) 43 | 44 | G_END_DECLS 45 | 46 | -------------------------------------------------------------------------------- /src/lib/polari-room.h: -------------------------------------------------------------------------------- 1 | /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 | /* 3 | * SPDX-FileCopyrightText: 2013 Red Hat, Inc. 4 | * 5 | * SPDX-License-Identifier: GPL-2.0-or-later 6 | */ 7 | 8 | #pragma once 9 | 10 | #include 11 | 12 | G_GNUC_BEGIN_IGNORE_DEPRECATIONS 13 | #include 14 | G_GNUC_END_IGNORE_DEPRECATIONS 15 | 16 | G_BEGIN_DECLS 17 | 18 | #define POLARI_TYPE_ROOM (polari_room_get_type()) 19 | G_DECLARE_FINAL_TYPE (PolariRoom, polari_room, POLARI, ROOM, GObject) 20 | 21 | const char *polari_room_get_channel_error (PolariRoom *room); 22 | void polari_room_set_channel_error (PolariRoom *room, 23 | const char *channel_error); 24 | 25 | gboolean polari_room_should_highlight_message (PolariRoom *room, 26 | const char *sender, 27 | const char *message); 28 | 29 | void polari_room_set_topic (PolariRoom *room, const char *topic); 30 | 31 | void polari_room_add_member (PolariRoom *room, TpContact *member); 32 | void polari_room_remove_member (PolariRoom *room, TpContact *member); 33 | 34 | void polari_room_send_identify_message_async (PolariRoom *room, 35 | const char *command, 36 | const char *username, 37 | const char *password, 38 | GAsyncReadyCallback callback, 39 | gpointer user_data); 40 | gboolean polari_room_send_identify_message_finish (PolariRoom *room, 41 | GAsyncResult *res, 42 | GError **error); 43 | 44 | char *polari_create_room_id (TpAccount *account, 45 | const char *name, 46 | TpHandleType type); 47 | 48 | G_END_DECLS 49 | -------------------------------------------------------------------------------- /src/lib/polari-tp-autocleanup.h: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2017 Florian Müllner 3 | * 4 | * SPDX-License-Identifier: GPL-2.0-or-later 5 | */ 6 | 7 | #pragma once 8 | 9 | #include 10 | 11 | G_BEGIN_DECLS 12 | 13 | G_DEFINE_AUTOPTR_CLEANUP_FUNC (TpAutomaticClientFactory, g_object_unref) 14 | G_DEFINE_AUTOPTR_CLEANUP_FUNC (TpMessage, g_object_unref) 15 | G_DEFINE_AUTOPTR_CLEANUP_FUNC (TpContactInfoField, tp_contact_info_field_free) 16 | 17 | G_END_DECLS 18 | -------------------------------------------------------------------------------- /src/lib/polari-tpl-importer.h: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2017 Florian Müllner 3 | * 4 | * SPDX-License-Identifier: GPL-2.0-or-later 5 | */ 6 | 7 | #pragma once 8 | 9 | #include 10 | 11 | #include 12 | 13 | G_BEGIN_DECLS 14 | 15 | #define POLARI_TYPE_TPL_IMPORTER (polari_tpl_importer_get_type()) 16 | 17 | G_DECLARE_FINAL_TYPE (PolariTplImporter, polari_tpl_importer, POLARI, TPL_IMPORTER, GObject) 18 | 19 | PolariTplImporter *polari_tpl_importer_new (void); 20 | 21 | void polari_tpl_importer_import_async (PolariTplImporter *self, 22 | GFile *file, 23 | GCancellable *cancellable, 24 | GAsyncReadyCallback callback, 25 | gpointer user_data); 26 | 27 | TrackerBatch *polari_tpl_importer_import_finish (PolariTplImporter *self, 28 | GAsyncResult *result, 29 | GError **error); 30 | 31 | void polari_tpl_importer_collect_files_async (PolariTplImporter *self, 32 | GCancellable *cancellable, 33 | GAsyncReadyCallback callback, 34 | gpointer user_data); 35 | 36 | GList *polari_tpl_importer_collect_files_finish (PolariTplImporter *self, 37 | GAsyncResult *result, 38 | GError **error); 39 | 40 | #define POLARI_TYPE_TPL_MESSAGE (polari_tpl_message_get_type()) 41 | 42 | G_END_DECLS 43 | -------------------------------------------------------------------------------- /src/lib/polari-util.c: -------------------------------------------------------------------------------- 1 | /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 | /* 3 | * SPDX-FileCopyrightText: 2015 Red Hat, Inc 4 | * 5 | * SPDX-License-Identifier: GPL-2.0-or-later 6 | */ 7 | 8 | #include 9 | 10 | #include "polari-util.h" 11 | 12 | #include 13 | 14 | /** 15 | * polari_util_get_basenick: 16 | * @nick: (transfer none): the original nick 17 | * 18 | * Returns: (transfer full): the "base nick" of @nick, which can be used to 19 | * group nicks that likely belong to the same person (e.g. "nick-away" or 20 | * "nick|bbl") 21 | */ 22 | char * 23 | polari_util_get_basenick (const char *nick) 24 | { 25 | int len; 26 | 27 | for (len = 0; g_ascii_isalnum(nick[len]); len++) 28 | ; 29 | 30 | if (len > 0) 31 | return g_utf8_casefold (nick, len); 32 | else 33 | return g_utf8_casefold (nick, -1); 34 | } 35 | 36 | #ifdef HAVE_STRCASESTR 37 | # define FOLDFUNC(text) ((char *)(text)) 38 | # define MATCHFUNC(haystick,needle) strcasestr (haystick, needle) 39 | #else 40 | # define FOLDFUNC(text) g_utf8_casefold (text, -1) 41 | # define MATCHFUNC(haystick,needle) strstr (haystick, needle) 42 | #endif 43 | 44 | gboolean 45 | polari_util_match_nick (const char *text, 46 | const char *nick) 47 | { 48 | g_autofree char *folded_text = NULL; 49 | g_autofree char *folded_nick = NULL; 50 | char *match; 51 | gboolean result = FALSE; 52 | int len; 53 | 54 | len = strlen (nick); 55 | if (len == 0) 56 | return FALSE; 57 | 58 | folded_text = FOLDFUNC (text); 59 | folded_nick = FOLDFUNC (nick); 60 | 61 | match = MATCHFUNC (folded_text, folded_nick); 62 | 63 | while (match != NULL) 64 | { 65 | gboolean starts_word, ends_word; 66 | 67 | /* assume ASCII nicknames, so no complex pango-style breaks */ 68 | starts_word = (match == folded_text || !g_ascii_isalnum (*(match - 1))); 69 | ends_word = !g_ascii_isalnum (*(match + len)); 70 | 71 | result = starts_word && ends_word; 72 | if (result) 73 | break; 74 | match = MATCHFUNC (match + len, folded_nick); 75 | } 76 | 77 | return result; 78 | } 79 | 80 | /** 81 | * polari_util_match_identify_message: 82 | * @message: a text message 83 | * @command: (optional) (out): the parsed command if the @message is an 84 | * identify command 85 | * @username: (optional) (out): the parsed name if the @message is an 86 | * identify command 87 | * @password: (optional) (out): the parsed password if the @message is an 88 | * identify command 89 | * 90 | * Returns: %TRUE if @message is an identify command 91 | */ 92 | gboolean 93 | polari_util_match_identify_message (const char *message, 94 | char **command, 95 | char **username, 96 | char **password) 97 | { 98 | static GRegex *identify_message_regex = NULL; 99 | g_autoptr(GMatchInfo) match = NULL; 100 | g_autofree char *text = NULL; 101 | char *stripped_text; 102 | gboolean matched; 103 | 104 | text = g_strdup (message); 105 | stripped_text = g_strstrip (text); 106 | 107 | if (G_UNLIKELY (identify_message_regex == NULL)) 108 | identify_message_regex = g_regex_new ("^(identify|login) (?:(\\S+) )?(\\S+)$", 109 | G_REGEX_OPTIMIZE | G_REGEX_CASELESS, 110 | 0, NULL); 111 | 112 | matched = g_regex_match (identify_message_regex, stripped_text, 0, &match); 113 | if (matched) 114 | { 115 | if (command) 116 | *command = g_match_info_fetch (match, 1); 117 | if (username) 118 | *username = g_match_info_fetch (match, 2); 119 | if (password) 120 | *password = g_match_info_fetch (match, 3); 121 | } 122 | 123 | return matched; 124 | } 125 | 126 | /** 127 | * polari_util_get_tracker_connection: 128 | * 129 | * Returns: (transfer none): 130 | */ 131 | TrackerSparqlConnection * 132 | polari_util_get_tracker_connection (GError **error) 133 | { 134 | static TrackerSparqlConnection *connection = NULL; 135 | 136 | if (connection == NULL) 137 | { 138 | g_autoptr(GFile) store = NULL; 139 | g_autoptr(GFile) ontology = NULL; 140 | g_autofree char *store_path = NULL; 141 | 142 | store_path = g_build_filename (g_get_user_data_dir (), 143 | "polari", 144 | "chatlogs.v1", 145 | NULL); 146 | store = g_file_new_for_path (store_path); 147 | ontology = g_file_new_for_uri ("resource:///org/gnome/Polari/ontologies/"); 148 | 149 | connection = tracker_sparql_connection_new (TRACKER_SPARQL_CONNECTION_FLAGS_FTS_ENABLE_STEMMER | 150 | TRACKER_SPARQL_CONNECTION_FLAGS_FTS_ENABLE_UNACCENT, 151 | store, 152 | ontology, 153 | NULL, 154 | error); 155 | } 156 | 157 | return connection; 158 | } 159 | 160 | /** 161 | * polari_util_close_tracker_connection: 162 | */ 163 | void 164 | polari_util_close_tracker_connection (void) 165 | { 166 | TrackerSparqlConnection *connection = NULL; 167 | 168 | connection = polari_util_get_tracker_connection (NULL); 169 | if (connection) 170 | { 171 | tracker_sparql_connection_close (connection); 172 | g_object_unref (connection); 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /src/lib/polari-util.h: -------------------------------------------------------------------------------- 1 | /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 | /* 3 | * SPDX-FileCopyrightText: 2015 Red Hat, Inc 4 | * 5 | * SPDX-License-Identifier: GPL-2.0-or-later 6 | */ 7 | 8 | #pragma once 9 | 10 | #include 11 | #include 12 | 13 | char *polari_util_get_basenick (const char *nick); 14 | 15 | gboolean polari_util_match_nick (const char *text, 16 | const char *nick); 17 | 18 | gboolean polari_util_match_identify_message (const char *message, 19 | char **command, 20 | char **username, 21 | char **password); 22 | 23 | TrackerSparqlConnection *polari_util_get_tracker_connection (GError **error); 24 | 25 | void polari_util_close_tracker_connection (void); 26 | -------------------------------------------------------------------------------- /src/logger.js: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2015 Florian Müllner 2 | // SPDX-FileCopyrightText: 2015 Carlos Garnacho 3 | // 4 | // SPDX-License-Identifier: GPL-2.0-or-later 5 | 6 | import Gio from 'gi://Gio'; 7 | import GLib from 'gi://GLib'; 8 | import Polari from 'gi://Polari'; 9 | import Tracker from 'gi://Tracker'; 10 | 11 | Gio._promisify(Tracker.SparqlStatement.prototype, 'execute_async'); 12 | Gio._promisify(Tracker.SparqlCursor.prototype, 'next_async'); 13 | Gio._promisify(Tracker.Batch.prototype, 'execute_async'); 14 | Gio._promisify(Polari.TplImporter.prototype, 'collect_files_async'); 15 | Gio._promisify(Polari.TplImporter.prototype, 'import_async'); 16 | 17 | class GenericQuery { 18 | constructor(query) { 19 | this._connection = Polari.util_get_tracker_connection(); 20 | this._results = []; 21 | this._closed = false; 22 | 23 | this._statement = 24 | this._connection.load_statement_from_gresource(query, null); 25 | } 26 | 27 | async execute(args, cancellable = null) { 28 | for (const name in args) { 29 | if (typeof args[name] === 'number') 30 | this._statement.bind_int(name, args[name]); 31 | else 32 | this._statement.bind_string(name, args[name]); 33 | } 34 | 35 | // eslint-disable-next-line no-return-await 36 | return await this._statement.execute_async(cancellable); 37 | } 38 | 39 | async next(cursor, cancellable = null) { 40 | if (!await cursor.next_async(cancellable)) { 41 | cursor.close(); 42 | return null; 43 | } 44 | 45 | return this._getRow(cursor); 46 | } 47 | 48 | _getColumnValue(cursor, col) { 49 | switch (cursor.get_value_type(col)) { 50 | case Tracker.SparqlValueType.STRING: 51 | case Tracker.SparqlValueType.URI: 52 | case Tracker.SparqlValueType.BLANK_NODE: 53 | return cursor.get_string(col)[0]; 54 | case Tracker.SparqlValueType.INTEGER: 55 | return cursor.get_integer(col); 56 | case Tracker.SparqlValueType.DOUBLE: 57 | return cursor.get_double(col); 58 | case Tracker.SparqlValueType.BOOLEAN: 59 | return cursor.get_boolean(col); 60 | case Tracker.SparqlValueType.DATETIME: 61 | return cursor.get_datetime(col); 62 | case Tracker.SparqlValueType.UNBOUND: 63 | return null; 64 | default: 65 | throw new Error('Unhandled result type'); 66 | } 67 | } 68 | 69 | _getRow(cursor) { 70 | const nCols = cursor.get_n_columns(); 71 | if (nCols === 1) 72 | return this._getColumnValue(cursor, 0); 73 | 74 | let value = {}; 75 | for (let i = 0; i < nCols; i++) { 76 | const name = cursor.get_variable_name(i); 77 | value[name] = this._getColumnValue(cursor, i); 78 | } 79 | return value; 80 | } 81 | } 82 | 83 | export class LogWalker { 84 | constructor(room) { 85 | this._room = room; 86 | this._query = null; 87 | this._isEnd = false; 88 | // Start with a date in the slight future 89 | this._lastTime = GLib.DateTime.new_now_utc().add_days(1); 90 | 91 | const accountId = this._room.account.get_path_suffix(); 92 | const roomName = this._room.channel_name; 93 | this._channelIri = `urn:channel:${accountId}:${roomName}`; 94 | } 95 | 96 | async getEvents(numEvents) { 97 | if (this._isEnd) 98 | return []; 99 | 100 | if (!this._query) { 101 | const query = '/org/gnome/Polari/sparql/get-room-events.rq'; 102 | this._query = new GenericQuery(query); 103 | } 104 | 105 | const channel = this._channelIri; 106 | const lastTime = this._lastTime.format_iso8601(); 107 | let cursor = 108 | await this._query.execute({channel, numEvents: numEvents * 2, lastTime}, null); 109 | let results = []; 110 | let event; 111 | let i = 0; 112 | 113 | // eslint-disable-next-line no-await-in-loop 114 | while ((event = await this._query.next(cursor)) !== null) { 115 | // Cluster events with the same time together, even if 116 | // we are at the numEvents limit. 117 | if (i > numEvents && 118 | event.time !== results[results.length - 1].time) 119 | break; 120 | 121 | i++; 122 | results.push(event); 123 | } 124 | 125 | this._isEnd = results.length < numEvents; 126 | 127 | if (!this._isEnd) 128 | this._lastTime = results[results.length - 1].time; 129 | 130 | return results.reverse().map(m => { 131 | const {text, senderNick, time, isAction, isSelf} = m; 132 | return new Polari.Message(text, senderNick, time, isAction, isSelf); 133 | }); 134 | } 135 | 136 | isEnd() { 137 | return this._isEnd; 138 | } 139 | } 140 | 141 | export class LogImporter { 142 | constructor() { 143 | this._connection = Polari.util_get_tracker_connection(); 144 | this._importer = new Polari.TplImporter(); 145 | } 146 | 147 | async init() { 148 | this._files = await this._importer.collect_files_async(null); 149 | return this._files.length; 150 | } 151 | 152 | async importNext() { 153 | try { 154 | const file = this._files.pop(); 155 | if (!file) 156 | return false; 157 | 158 | let batch = await this._importer.import_async(file, null); 159 | 160 | return await batch.execute_async(null); 161 | } catch (e) { 162 | console.debug(e); 163 | return true; 164 | } 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2013 Florian Müllner 2 | // 3 | // SPDX-License-Identifier: GPL-2.0-or-later 4 | 5 | import GLib from 'gi://GLib'; 6 | 7 | import * as Config from './config.js'; 8 | import Polari from 'gi://Polari'; 9 | import {ngettext} from 'gettext'; 10 | import {programInvocationName, programArgs} from 'system'; 11 | 12 | imports.package.init({ 13 | name: Config.PACKAGE_NAME, 14 | version: Config.PACKAGE_VERSION, 15 | prefix: Config.PREFIX, 16 | libdir: Config.LIBDIR, 17 | }); 18 | 19 | pkg.initGettext(); 20 | globalThis.ngettext = ngettext; 21 | 22 | // eslint-disable-next-line no-restricted-properties 23 | globalThis.vprintf = (fmt, ...args) => imports.format.vprintf(fmt, args); 24 | 25 | pkg.require({ 26 | 'GdkPixbuf': '2.0', 27 | 'GObject': '2.0', 28 | 'Gtk': '4.0', 29 | 'Pango': '1.0', 30 | 'PangoCairo': '1.0', 31 | 'Secret': '1', 32 | 'TelepathyGLib': '0.12', 33 | 'Tracker': '3.0', 34 | }); 35 | pkg.requireSymbol('Adw', '1', 'ToolbarView'); 36 | pkg.checkSymbol('Soup', '3.0'); 37 | 38 | import Application from './application.js'; 39 | 40 | let application = new Application(); 41 | if (GLib.getenv('POLARI_PERSIST')) 42 | application.hold(); 43 | application.run([programInvocationName, ...programArgs]); 44 | Polari.util_close_tracker_connection(); 45 | -------------------------------------------------------------------------------- /src/meson.build: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2016 Florian Müllner 2 | # 3 | # SPDX-License-Identifier: GPL-2.0-or-later 4 | 5 | config_js = vcs_tag( 6 | command: ['git', 'describe'], 7 | input: configure_file( 8 | configuration: { 9 | 'PACKAGE_NAME': meson.project_name(), 10 | 'PACKAGE_VERSION': '@VCS_TAG@', 11 | 'PREFIX': prefix, 12 | 'LIBDIR': libdir, 13 | }, 14 | input: 'config.js.in', 15 | output: 'config.js.in', 16 | ), 17 | output: 'config.js', 18 | ) 19 | 20 | src_resources = gnome.compile_resources( 21 | 'src-resources', 22 | '@0@.src.gresource.xml'.format(app_id), 23 | dependencies: config_js, 24 | c_name: 'src_resources', 25 | ) 26 | 27 | polari = executable( 28 | 'polari', 29 | ['polari.c', src_resources, data_resources], 30 | dependencies: [config_h, gio, girepository, gjs], 31 | install: true, 32 | ) 33 | 34 | libsources = [ 35 | 'lib/polari-client-factory.c', 36 | 'lib/polari-client-factory.h', 37 | 'lib/polari-message.c', 38 | 'lib/polari-message.h', 39 | 'lib/polari-room.c', 40 | 'lib/polari-room.h', 41 | 'lib/polari-tpl-importer.c', 42 | 'lib/polari-tpl-importer.h', 43 | 'lib/polari-util.c', 44 | 'lib/polari-util.h', 45 | ] 46 | 47 | lib_nongir_sources = [ 48 | 'lib/polari-message-private.h', 49 | 'lib/polari-tp-autocleanup.h', 50 | ] 51 | 52 | libargs = ['-DG_LOG_USE_STRUCTURED', '-DG_LOG_DOMAIN="Polari"'] 53 | libpolari = shared_library( 54 | 'polari-1.0', 55 | libsources + lib_nongir_sources + lib_resources, 56 | dependencies: [gio, telepathy_glib, tracker], 57 | c_args: libargs, 58 | install: true, 59 | install_dir: pkglibdir, 60 | ) 61 | 62 | libpolari_dep = declare_dependency( 63 | include_directories: include_directories('lib'), 64 | dependencies: [gio, telepathy_glib, tracker], 65 | link_with: libpolari, 66 | ) 67 | 68 | gnome.generate_gir( 69 | libpolari, 70 | sources: libsources, 71 | nsversion: '1.0', 72 | namespace: 'Polari', 73 | symbol_prefix: 'polari', 74 | identifier_prefix: 'Polari', 75 | includes: ['Gio-2.0', 'TelepathyGLib-0.12', 'Tracker-3.0'], 76 | extra_args: '--quiet', 77 | install_dir_gir: girdir, 78 | install_dir_typelib: typelibdir, 79 | install: true, 80 | ) 81 | 82 | subdir('tests') 83 | 84 | install_data('thumbnailer.js', install_dir: pkgdatadir) 85 | -------------------------------------------------------------------------------- /src/networksManager.js: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2016 Florian Müllner 2 | // SPDX-FileCopyrightText: 2016 raresv 3 | // SPDX-FileCopyrightText: 2016 Kunaal Jain 4 | // 5 | // SPDX-License-Identifier: GPL-2.0-or-later 6 | 7 | import Gio from 'gi://Gio'; 8 | import GLib from 'gi://GLib'; 9 | 10 | const Signals = imports.signals; 11 | 12 | export default class NetworksManager { 13 | static getDefault() { 14 | if (!this._singleton) 15 | this._singleton = new NetworksManager(); 16 | return this._singleton; 17 | } 18 | 19 | constructor() { 20 | this._networks = []; 21 | this._networksById = new Map(); 22 | 23 | let uri = 'resource:///org/gnome/Polari/data/networks.json'; 24 | let file = Gio.File.new_for_uri(uri); 25 | let data; 26 | try { 27 | [, data] = file.load_contents(null); 28 | this._parseNetworks(new TextDecoder().decode(data)); 29 | } catch (e) { 30 | console.warn('Failed to load network list'); 31 | console.debug(e); 32 | } 33 | } 34 | 35 | _parseNetworks(data) { 36 | let networks; 37 | try { 38 | networks = JSON.parse(data); 39 | } catch (e) { 40 | console.warn('Failed to parse network list'); 41 | console.debug(e); 42 | return false; 43 | } 44 | 45 | this._networksById.clear(); 46 | this._networks = networks; 47 | this._networks.forEach(network => { 48 | this._networksById.set(network.id, network); 49 | }); 50 | return true; 51 | } 52 | 53 | _lookupNetwork(id) { 54 | let network = this._networksById.get(id); 55 | if (!network) 56 | throw new Error('Invalid network ID'); 57 | return network; 58 | } 59 | 60 | get networks() { 61 | return this._networks; 62 | } 63 | 64 | getAccountIsPredefined(account) { 65 | return account && this._networksById.has(account.service); 66 | } 67 | 68 | getNetworkName(id) { 69 | return this._lookupNetwork(id).name; 70 | } 71 | 72 | getNetworkIsFavorite(id) { 73 | let network = this._lookupNetwork(id); 74 | 75 | if (Object.prototype.hasOwnProperty.call(network, 'favorite')) 76 | return network['favorite']; 77 | 78 | return false; 79 | } 80 | 81 | getNetworkDetails(id) { 82 | let network = this._lookupNetwork(id); 83 | if (!network.servers || !network.servers.length) 84 | throw new Error(`No servers for network ${id}`); 85 | 86 | let server = this.getNetworkServers(id)[0]; 87 | let details = { 88 | 'account': new GLib.Variant('s', GLib.get_user_name()), 89 | 'server': new GLib.Variant('s', server.address), 90 | 'port': new GLib.Variant('u', server.port), 91 | 'use-ssl': new GLib.Variant('b', server.ssl), 92 | }; 93 | 94 | if (server.charset) 95 | details['charset'] = new GLib.Variant('s', server.charset); 96 | 97 | return details; 98 | } 99 | 100 | getNetworkServers(id) { 101 | let network = this._lookupNetwork(id); 102 | let sslServers = network.servers.filter(s => s.ssl); 103 | return sslServers.length > 0 ? sslServers : network.servers.slice(); 104 | } 105 | 106 | getNetworkMatchTerms(id) { 107 | let network = this._lookupNetwork(id); 108 | let servers = network.servers.map(s => s.address); 109 | let terms = [network.name, network.id, ...servers]; 110 | return terms.map(t => t.toLowerCase()); 111 | } 112 | 113 | findByServer(server) { 114 | let network = this._networks.find(n => { 115 | return n.servers.some(s => s.address === server); 116 | }); 117 | return network ? network.id : null; 118 | } 119 | } 120 | Signals.addSignalMethods(NetworksManager.prototype); 121 | -------------------------------------------------------------------------------- /src/org.gnome.Polari.src.gresource.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 10 | 11 | config.js 12 | accountsMonitor.js 13 | application.js 14 | chatView.js 15 | connections.js 16 | entryArea.js 17 | initialSetup.js 18 | ircParser.js 19 | joinDialog.js 20 | logger.js 21 | main.js 22 | mainWindow.js 23 | networksManager.js 24 | pasteManager.js 25 | roomList.js 26 | roomManager.js 27 | roomStack.js 28 | serverRoomManager.js 29 | tabCompletion.js 30 | telepathyClient.js 31 | urlPreview.js 32 | userList.js 33 | utils.js 34 | userTracker.js 35 | 36 | 37 | -------------------------------------------------------------------------------- /src/pasteManager.js: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2013 Florian Müllner 2 | // SPDX-FileCopyrightText: 2016 Kunaal Jain 3 | // 4 | // SPDX-License-Identifier: GPL-2.0-or-later 5 | 6 | import Gdk from 'gi://Gdk'; 7 | import GdkPixbuf from 'gi://GdkPixbuf'; 8 | import Gio from 'gi://Gio'; 9 | import GLib from 'gi://GLib'; 10 | import GObject from 'gi://GObject'; 11 | import Gtk from 'gi://Gtk'; 12 | 13 | import * as Utils from './utils.js'; 14 | 15 | Gio._promisify(Gio._LocalFilePrototype, 16 | 'load_contents_async', 'load_contents_finish'); 17 | Gio._promisify(Gio._LocalFilePrototype, 18 | 'query_info_async', 'query_info_finish'); 19 | Gio._promisify(Gio._LocalFilePrototype, 'read_async', 'read_finish'); 20 | Gio._promisify(GdkPixbuf.Pixbuf, 21 | 'new_from_stream_async', 'new_from_stream_finish'); 22 | 23 | /** 24 | * Find a supported GType for the formats of a contents exchange 25 | * that is being negotiated 26 | * 27 | * @param {Gdk.ContentFormats} formats - provided formats 28 | * @returns {GType=} - the matching GType 29 | */ 30 | export function gtypeFromFormats(formats) { 31 | const builder = new Gdk.ContentFormatsBuilder(); 32 | builder.add_gtype(Gio.File); 33 | builder.add_gtype(GdkPixbuf.Pixbuf); 34 | builder.add_gtype(GObject.TYPE_STRING); 35 | const supportedFormats = builder.to_formats(); 36 | 37 | return supportedFormats.match_gtype(formats.union_deserialize_gtypes()); 38 | } 39 | 40 | export default class PasteManager { 41 | pasteContent(content, title) { 42 | if (typeof content === 'string') 43 | return Utils.gpaste(content, title); 44 | else if (content instanceof GdkPixbuf.Pixbuf) 45 | return Utils.imgurPaste(content, title); 46 | else if (content.query_info_async) 47 | return this._pasteFile(content, title); 48 | else 49 | throw new Error('Unhandled content type'); 50 | } 51 | 52 | async _pasteFile(file, title) { 53 | const fileInfo = await file.query_info_async( 54 | Gio.FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, 55 | Gio.FileQueryInfoFlags.NONE, 56 | GLib.PRIORITY_DEFAULT, null); 57 | 58 | const contentType = fileInfo.get_content_type(); 59 | if (Gio.content_type_is_a(contentType, 'text/plain')) { 60 | const [contents] = await file.load_contents_async(null); 61 | return Utils.gpaste(new TextDecoder().decode(contents), title); 62 | } else if (Gio.content_type_is_a(contentType, 'image/*')) { 63 | const stream = await file.read_async(GLib.PRIORITY_DEFAULT, null); 64 | const pixbuf = 65 | await GdkPixbuf.Pixbuf.new_from_stream_async(stream, null); 66 | return Utils.imgurPaste(pixbuf, title); 67 | } else { 68 | throw new Error('Unhandled content type'); 69 | } 70 | } 71 | } 72 | 73 | export const DropTargetIface = GObject.registerClass( 74 | class DropTargetIface extends GObject.Interface { 75 | static [GObject.requires] = [GObject.Object]; 76 | static [GObject.properties] = { 77 | 'can-drop': GObject.ParamSpec.boolean( 78 | 'can-drop', 'can-drop', 'can-drop', 79 | GObject.ParamFlags.READABLE, 80 | false), 81 | }; 82 | 83 | static [GObject.signals] = { 84 | 'text-dropped': {param_types: [GObject.TYPE_STRING]}, 85 | 'image-dropped': {param_types: [GdkPixbuf.Pixbuf]}, 86 | 'file-dropped': {param_types: [Gio.File]}, 87 | }; 88 | 89 | addTargets(widget) { 90 | const imageTypes = []; 91 | for (const f of GdkPixbuf.Pixbuf.get_formats()) 92 | imageTypes.push(...f.get_mime_types()); 93 | 94 | this._dropTarget = new Gtk.DropTargetAsync({ 95 | actions: Gdk.DragAction.COPY, 96 | formats: new Gdk.ContentFormats([ 97 | 'text/plain', 98 | 'text/uri-list', 99 | ...imageTypes, 100 | ]), 101 | }); 102 | this._dropTarget.connect('drop', (_, drop) => { 103 | this._handleDrop(drop); 104 | return true; 105 | }); 106 | this._dropTarget.connect('accept', (_, drop) => { 107 | if (!this.can_drop) 108 | return false; 109 | return this._dropTarget.formats.match(drop.get_formats()); 110 | }); 111 | widget.add_controller(this._dropTarget); 112 | } 113 | 114 | async _handleDrop(drop) { 115 | const type = gtypeFromFormats(drop.formats); 116 | const value = await this._readDropValue(drop, type); 117 | let action = Gdk.DragAction.COPY; 118 | if (typeof value === 'string') { 119 | this.emit('text-dropped', value); 120 | } else if (value instanceof GdkPixbuf.Pixbuf) { 121 | this.emit('image-dropped', value); 122 | } else if (value instanceof Gio.File) { 123 | if (await this._canHandleFile(value)) 124 | this.emit('file-dropped', value); 125 | else 126 | action = 0; 127 | } else { 128 | console.warn(`Unexpected drop value ${value}`); 129 | } 130 | drop.finish(action); 131 | } 132 | 133 | async _canHandleFile(file) { 134 | let contentType = ''; 135 | try { 136 | const fileInfo = await file.query_info_async( 137 | Gio.FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, 138 | Gio.FileQueryInfoFlags.NONE, 139 | GLib.PRIORITY_DEFAULT, 140 | null); 141 | contentType = fileInfo.get_content_type(); 142 | } catch (e) { 143 | console.log(`Failed to determine content type: ${e}`); 144 | } 145 | 146 | const mimes = this._dropTarget.formats.get_mime_types(); 147 | return mimes.some(mime => Gio.content_type_is_a(contentType, mime)); 148 | } 149 | 150 | _readDropValue(drop, type) { 151 | return new Promise((resolve, reject) => { 152 | drop.read_value_async(type, 0, null, (_, res) => { 153 | try { 154 | const value = drop.read_value_finish(res); 155 | resolve(value); 156 | } catch (e) { 157 | reject(e); 158 | } 159 | }); 160 | }); 161 | } 162 | }); 163 | -------------------------------------------------------------------------------- /src/polari.c: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2016 Florian Müllner 3 | * 4 | * SPDX-License-Identifier: GPL-2.0-or-later 5 | */ 6 | #include 7 | #include 8 | 9 | #include "config.h" 10 | 11 | #ifndef GJS_HAS_AUTOCLEANUP 12 | G_DEFINE_AUTOPTR_CLEANUP_FUNC (GjsContext, g_object_unref) 13 | #endif 14 | 15 | #define JS_MAIN "resource:///org/gnome/Polari/js/main.js" 16 | 17 | static char ** 18 | get_js_argv (int argc, const char * const *argv) 19 | { 20 | char * injected_args[] = { 21 | #ifdef SNAPSHOT 22 | "--test-instance", 23 | #endif 24 | NULL 25 | }; 26 | char **strv; 27 | guint js_argc = argc - 1; // gjs doesn't do argv[0] 28 | guint i; 29 | 30 | strv = g_new0 (char *, js_argc + G_N_ELEMENTS (injected_args) + 1); 31 | for (i = 0; i < js_argc; i++) 32 | strv[i] = g_strdup (argv[i + 1]); 33 | for (i = 0; i < G_N_ELEMENTS (injected_args); i++) 34 | strv[js_argc + i] = g_strdup (injected_args[i]); 35 | return strv; 36 | } 37 | 38 | static gboolean 39 | get_profiler_fd (int *fd_p) 40 | { 41 | const char *enabled; 42 | const char *fd_str; 43 | int fd; 44 | 45 | /* Sysprof uses the "GJS_TRACE_FD=N" environment variable to connect GJS 46 | * profiler data to the combined Sysprof capture. Since we are in control of 47 | * the GjsContext, we need to proxy this FD across to the GJS profiler. 48 | */ 49 | 50 | fd_str = g_getenv ("GJS_TRACE_FD"); 51 | enabled = g_getenv ("GJS_ENABLE_PROFILER"); 52 | if (fd_str == NULL || enabled == NULL) 53 | return FALSE; 54 | 55 | fd = atoi (fd_str); 56 | 57 | if (fd <= 2) 58 | return FALSE; 59 | 60 | *fd_p = fd; 61 | return TRUE; 62 | } 63 | 64 | int 65 | main (int argc, char *argv[]) 66 | { 67 | const char *search_path[] = { "resource:///org/gnome/Polari/js", NULL }; 68 | g_autoptr (GOptionContext) option_context = NULL; 69 | g_autoptr (GError) error = NULL; 70 | g_autoptr (GjsContext) context = NULL; 71 | g_auto (GStrv) js_argv = NULL; 72 | GjsProfiler *profiler = NULL; 73 | gboolean debugger = FALSE; 74 | int profiler_fd; 75 | uint8_t status; 76 | 77 | GOptionEntry entries[] = 78 | { 79 | { "debugger", 'd', 0, G_OPTION_ARG_NONE, &debugger, NULL, NULL }, 80 | { NULL } 81 | }; 82 | 83 | #ifdef SNAPSHOT 84 | g_set_application_name ("Polari Development Snapshot"); 85 | #else 86 | g_set_application_name ("Polari"); 87 | #endif 88 | 89 | g_irepository_prepend_search_path (PKGLIBDIR "/girepository-1.0"); 90 | g_irepository_prepend_library_path (PKGLIBDIR); 91 | 92 | context = g_object_new (GJS_TYPE_CONTEXT, 93 | "search-path", search_path, 94 | NULL); 95 | 96 | option_context = g_option_context_new (""); 97 | g_option_context_set_help_enabled (option_context, FALSE); 98 | g_option_context_set_ignore_unknown_options (option_context, TRUE); 99 | g_option_context_add_main_entries (option_context, entries, NULL); 100 | 101 | g_option_context_parse (option_context, &argc, &argv, NULL); 102 | 103 | if (debugger) 104 | gjs_context_setup_debugger_console (context); 105 | 106 | js_argv = get_js_argv (argc, (const char * const *)argv); 107 | gjs_context_set_argv (context, g_strv_length (js_argv), (const char **)js_argv); 108 | 109 | if (get_profiler_fd (&profiler_fd)) 110 | { 111 | profiler = gjs_context_get_profiler (context); 112 | 113 | gjs_profiler_set_fd (profiler, profiler_fd); 114 | gjs_profiler_start (profiler); 115 | } 116 | 117 | if (!gjs_context_register_module (context, "
", JS_MAIN, &error) || 118 | !gjs_context_eval_module (context, "
", &status, &error)) 119 | g_message ("Execution of main.js threw exception: %s", error->message); 120 | 121 | if (profiler) 122 | gjs_profiler_stop (profiler); 123 | 124 | return status; 125 | } 126 | -------------------------------------------------------------------------------- /src/tests/meson.build: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2018 Florian Müllner 2 | # 3 | # SPDX-License-Identifier: GPL-2.0-or-later 4 | 5 | test_env = [ 6 | 'G_TEST_SRCDIR=@0@'.format(meson.current_source_dir()), 7 | 'G_TEST_BUILDDIR=@0@'.format(meson.current_build_dir()), 8 | ] 9 | 10 | test_util = executable('test-util', 'test-util.c', dependencies: libpolari_dep) 11 | test('Testing utility functions', test_util, env: test_env) 12 | -------------------------------------------------------------------------------- /src/urlPreview.js: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2019 daronion 2 | // SPDX-FileCopyrightText: 2019 Florian Müllner 3 | // SPDX-FileCopyrightText: 2020 Philip Withnall 4 | // 5 | // SPDX-License-Identifier: GPL-2.0-or-later 6 | 7 | import GdkPixbuf from 'gi://GdkPixbuf'; 8 | import Gio from 'gi://Gio'; 9 | import GLib from 'gi://GLib'; 10 | import GObject from 'gi://GObject'; 11 | import Gtk from 'gi://Gtk'; 12 | import Pango from 'gi://Pango'; 13 | 14 | Gio._promisify(Gio._LocalFilePrototype, 'query_info_async', 'query_info_finish'); 15 | Gio._promisify(Gio.Subprocess.prototype, 'wait_async', 'wait_finish'); 16 | 17 | class Thumbnailer { 18 | static getDefault() { 19 | if (!this._singleton) 20 | this._singleton = new Thumbnailer(); 21 | return this._singleton; 22 | } 23 | 24 | constructor() { 25 | this._urlQueue = []; 26 | this._subProc = null; 27 | this._thumbnailsDir = `${GLib.get_user_cache_dir()}/polari/thumbnails/`; 28 | 29 | GLib.mkdir_with_parents(this._thumbnailsDir, 0o755); 30 | } 31 | 32 | get _hasNetwork() { 33 | const monitor = Gio.NetworkMonitor.get_default(); 34 | return monitor.state_valid && monitor.network_available && !monitor.network_metered; 35 | } 36 | 37 | getThumbnail(uri) { 38 | return new Promise((resolve, reject) => { 39 | const filename = this._generateFilename(uri); 40 | const data = {uri, filename, resolve, reject}; 41 | 42 | this._processData(data); 43 | }); 44 | } 45 | 46 | async _processData(data) { 47 | if (await this._thumbExists(data)) 48 | this._generationDone(data); 49 | else if (!this._hasNetwork) 50 | this._generationUnavailable(data); 51 | else if (!this._subProc) 52 | this._generateThumbnail(data); 53 | else 54 | this._urlQueue.push(data); 55 | } 56 | 57 | _generationDone(data, error = null) { 58 | if (error) 59 | data.reject(error); 60 | else 61 | data.resolve(data.filename); 62 | 63 | let nextData = this._urlQueue.shift(); 64 | if (nextData) 65 | this._processData(nextData); 66 | } 67 | 68 | _generationUnavailable(data) { 69 | this._generationDone(data, new Gio.IOErrorEnum({ 70 | code: Gio.IOErrorEnum.NETWORK_UNREACHABLE, 71 | message: 'Network unreachable', 72 | })); 73 | } 74 | 75 | async _generateThumbnail(data) { 76 | let {filename, uri} = data; 77 | this._subProc = Gio.Subprocess.new( 78 | ['gjs', '--module', `${pkg.pkgdatadir}/thumbnailer.js`, uri, filename], 79 | Gio.SubprocessFlags.NONE); 80 | try { 81 | await this._subProc.wait_async(null); 82 | this._generationDone(data); 83 | } catch (e) { 84 | this._generationDone(data, e); 85 | } 86 | this._subProc = null; 87 | } 88 | 89 | async _thumbExists(data) { 90 | const file = Gio.File.new_for_path(`${data.filename}`); 91 | try { 92 | await file.query_info_async(Gio.FILE_ATTRIBUTE_STANDARD_TYPE, 93 | Gio.FileQueryInfoFlags.NONE, GLib.PRIORITY_DEFAULT, null); 94 | return true; 95 | } catch (e) { 96 | return false; 97 | } 98 | } 99 | 100 | _generateFilename(url) { 101 | let checksum = GLib.Checksum.new(GLib.ChecksumType.MD5); 102 | checksum.update(url); 103 | 104 | return `${this._thumbnailsDir}${checksum.get_string()}.png`; 105 | } 106 | } 107 | 108 | export default GObject.registerClass( 109 | class URLPreview extends Gtk.Box { 110 | static [GObject.properties] = { 111 | 'uri': GObject.ParamSpec.string( 112 | 'uri', 'uri', 'uri', 113 | GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY, 114 | null), 115 | }; 116 | 117 | constructor(params) { 118 | super(params); 119 | 120 | this.set({ 121 | orientation: Gtk.Orientation.VERTICAL, 122 | spacing: 6, 123 | }); 124 | 125 | this.add_css_class('url-preview'); 126 | this.add_css_class('background'); 127 | 128 | this._imageLoaded = false; 129 | this._image = new Gtk.Image(); 130 | this._image.add_css_class('dim-label'); 131 | this.append(this._image); 132 | 133 | this._label = new Gtk.Label({ 134 | halign: Gtk.Align.START, 135 | ellipsize: Pango.EllipsizeMode.END, 136 | }); 137 | this._label.add_css_class('dim-label'); 138 | this.append(this._label); 139 | 140 | this._networkMonitor = Gio.NetworkMonitor.get_default(); 141 | this._networkChangedId = this._networkMonitor.connect('network-changed', 142 | this._maybeLoadImage.bind(this)); 143 | 144 | this.connect('destroy', this._onDestroy.bind(this)); 145 | } 146 | 147 | _onDestroy() { 148 | if (this._networkChangedId) 149 | this._networkMonitor.disconnect(this._networkChangedId); 150 | this._networkChangedId = 0; 151 | } 152 | 153 | async _maybeLoadImage() { 154 | if (this._imageLoaded || !this.get_mapped()) 155 | return; 156 | 157 | this._imageLoaded = true; 158 | this._image.set({ 159 | icon_name: 'image-loading-symbolic', 160 | pixel_size: 16, 161 | }); 162 | const thumbnailer = Thumbnailer.getDefault(); 163 | 164 | let title; 165 | try { 166 | const filename = await thumbnailer.getThumbnail(this.uri); 167 | const pixbuf = GdkPixbuf.Pixbuf.new_from_file(filename); 168 | 169 | title = pixbuf.get_option('tEXt::Title'); 170 | this._image.set_from_pixbuf(pixbuf); 171 | this._image.remove_css_class('dim-label'); 172 | } catch (e) { 173 | if (e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.NETWORK_UNREACHABLE)) { 174 | this._imageLoaded = false; 175 | } else { 176 | console.info(`Failed to generate thumbnail for ${this.uri}`); 177 | console.debug(e); 178 | } 179 | this._image.set({ 180 | icon_name: 'image-x-generic-symbolic', 181 | pixel_size: 64, 182 | }); 183 | } 184 | 185 | if (title) { 186 | this._label.set_label(title); 187 | this.tooltip_text = title; 188 | } 189 | } 190 | 191 | vfunc_map() { 192 | super.vfunc_map(); 193 | this._maybeLoadImage(); 194 | } 195 | }); 196 | --------------------------------------------------------------------------------