diff --git a/pkgs/by-name/si/signal-desktop-source/libsignal-node.nix b/pkgs/by-name/si/signal-desktop-source/libsignal-node.nix new file mode 100644 index 000000000000..5f07a3457af6 --- /dev/null +++ b/pkgs/by-name/si/signal-desktop-source/libsignal-node.nix @@ -0,0 +1,87 @@ +{ + stdenv, + rustPlatform, + fetchNpmDeps, + npmHooks, + protobuf, + clang, + gitMinimal, + cmake, + boringssl, + runCommand, + fetchFromGitHub, + python3, + nodejs, + +}: +let + # boring-sys expects the static libraries in build/ instead of lib/ + boringssl-wrapper = runCommand "boringssl-wrapper" { } '' + mkdir $out + cd $out + ln -s ${boringssl.out}/lib build + ln -s ${boringssl.dev}/include include + ''; +in +rustPlatform.buildRustPackage (finalAttrs: { + pname = "libsignal-node"; + version = "0.67.0"; + + src = fetchFromGitHub { + owner = "signalapp"; + repo = "libsignal"; + tag = "v${finalAttrs.version}"; + hash = "sha256-Cwra5o9g2+M3dboQUuBikRWyaydL/CGwOfJbImsFRoI="; + }; + useFetchCargoVendor = true; + cargoHash = "sha256-KVt4ESHaHDtD+pcaMMG/DMMznYGpWleEf7uTZB45Ud0="; + + npmRoot = "node"; + npmDeps = fetchNpmDeps { + name = "${finalAttrs.pname}-npm-deps"; + inherit (finalAttrs) version src; + sourceRoot = "${finalAttrs.src.name}/${finalAttrs.npmRoot}"; + hash = "sha256-TXRZFfdl86PLJ1FWOkTcXjbx8aZV7gEvs22Pu8b5uFU="; + }; + + nativeBuildInputs = [ + python3 + protobuf + nodejs + clang + gitMinimal + cmake + npmHooks.npmConfigHook + rustPlatform.bindgenHook + ]; + env.BORING_BSSL_PATH = "${boringssl-wrapper}"; + env.NIX_LDFLAGS = if stdenv.hostPlatform.isDarwin then "-lc++" else "-lstdc++"; + + patchPhase = '' + runHook prePatch + + substituteInPlace node/binding.gyp \ + --replace-fail "'--out-dir', '<(PRODUCT_DIR)/'," \ + "'--out-dir', '$out/lib/<(NODE_OS_NAME)-<(target_arch)/'," \ + --replace-fail "'target_name': 'libsignal_client_<(NODE_OS_NAME)_<(target_arch).node'," \ + "'target_name': '@signalapp+libsignal-client'," + + substituteInPlace node/build_node_bridge.py \ + --replace-fail "dst_base = 'libsignal_client_%s_%s' % (node_os_name, node_arch)" \ + "dst_base = '@signalapp+libsignal-client'" + + runHook postPatch + ''; + + buildPhase = '' + runHook preBuild + + pushd node + npx node-gyp rebuild + popd + + runHook postBuild + ''; + + dontCargoInstall = true; +}) diff --git a/pkgs/by-name/si/signal-desktop-source/package.nix b/pkgs/by-name/si/signal-desktop-source/package.nix new file mode 100644 index 000000000000..a085ec0fe610 --- /dev/null +++ b/pkgs/by-name/si/signal-desktop-source/package.nix @@ -0,0 +1,228 @@ +{ + stdenv, + lib, + nodejs_20, + pnpm_10, + electron_34, + python3, + makeWrapper, + callPackage, + libpulseaudio, + fetchFromGitHub, + runCommand, + fetchzip, + autoPatchelfHook, + makeDesktopItem, + copyDesktopItems, + replaceVars, + noto-fonts-color-emoji, + nixosTests, + withAppleEmojis ? false, +}: +let + nodejs = nodejs_20; + pnpm = pnpm_10; + electron = electron_34; + + electron-headers = runCommand "electron-headers" { } '' + mkdir -p $out + tar -C $out --strip-components=1 -xvf ${electron.headers} + ''; + + sqlcipher-signal-extension = callPackage ./sqlcipher-signal-extension.nix { }; + libsignal-node = callPackage ./libsignal-node.nix { inherit nodejs; }; + + ringrtc = stdenv.mkDerivation (finalAttrs: { + pname = "ringrtc-bin"; + version = "2.50.1"; + src = fetchzip { + url = "https://build-artifacts.signal.org/libraries/ringrtc-desktop-build-v${finalAttrs.version}.tar.gz"; + hash = "sha256-KHNTw5ScBdYAAyKFdJ6PTmFr+7GYHqgnb4mmNUJZvzM="; + }; + + installPhase = '' + cp -r . $out + ''; + + nativeBuildInputs = [ autoPatchelfHook ]; + buildInputs = [ libpulseaudio ]; + meta = { + homepage = "https://github.com/signalapp/ringrtc"; + license = lib.licenses.agpl3Only; + sourceProvenance = with lib.sourceTypes; [ binaryNativeCode ]; + }; + }); + + # Noto Color Emoji PNG files for emoji replacement; see below. + noto-fonts-color-emoji-png = noto-fonts-color-emoji.overrideAttrs (prevAttrs: { + pname = "noto-fonts-color-emoji-png"; + + # The build produces 136×128 PNGs by default for arcane font + # reasons, but we want square PNGs. + buildFlags = prevAttrs.buildFlags or [ ] ++ [ "BODY_DIMENSIONS=128x128" ]; + + makeTargets = [ "compressed" ]; + + installPhase = '' + runHook preInstall + + mkdir -p $out/share + mv build/compressed_pngs $out/share/noto-fonts-color-emoji-png + python3 add_aliases.py --srcdir=$out/share/noto-fonts-color-emoji-png + + runHook postInstall + ''; + }); + +in +stdenv.mkDerivation (finalAttrs: { + pname = "signal-desktop-source"; + version = "7.46.0"; + + src = fetchFromGitHub { + owner = "signalapp"; + repo = "Signal-Desktop"; + tag = "v${finalAttrs.version}"; + hash = "sha256-pV28jcIQcPjyZL8q+gisnlfAGf0SOKDQ7OxacTM3B0M="; + }; + + nativeBuildInputs = [ + nodejs + (pnpm.override { inherit nodejs; }).configHook + makeWrapper + copyDesktopItems + python3 + ]; + buildInputs = (lib.optional (!withAppleEmojis) noto-fonts-color-emoji-png); + + patches = lib.optional (!withAppleEmojis) ( + replaceVars ./replace-apple-emoji-with-noto-emoji.patch { + noto-emoji-pngs = "${noto-fonts-color-emoji-png}/share/noto-fonts-color-emoji-png"; + } + ); + + pnpmDeps = pnpm.fetchDeps { + inherit (finalAttrs) + pname + version + src + patches + ; + hash = + if withAppleEmojis then + "sha256-keG+ymMD4ma0dt6N4Fai9u0+rh9VzkQD6tClPKoQXkM=" + else + "sha256-qImY0s8UQmuKGf8dvgO3YrJlrqqdoZtvbtLgvgMVnnE="; + }; + + env = { + ELECTRON_SKIP_BINARY_DOWNLOAD = "1"; + SIGNAL_ENV = "production"; + SOURCE_DATE_EPOCH = 1741810629; + }; + + preBuild = '' + cp ${sqlcipher-signal-extension}/share/sqlite3.gyp node_modules/@signalapp/better-sqlite3/deps/sqlite3.gyp + + cp -r ${ringrtc} node_modules/@signalapp/ringrtc/build + + rm -fr node_modules/@signalapp/libsignal-client/prebuilds + cp -r ${libsignal-node}/lib node_modules/@signalapp/libsignal-client/prebuilds + ''; + + buildPhase = '' + runHook preBuild + + export npm_config_nodedir=${electron-headers} + cp -r ${electron.dist} electron-dist + chmod -R u+w electron-dist + + pnpm run generate + pnpm exec electron-builder \ + --dir \ + --config.extraMetadata.environment=$SIGNAL_ENV \ + -c.electronDist=electron-dist \ + -c.electronVersion=${electron.version} + + runHook postBuild + ''; + + installPhase = '' + runHook preInstall + + mkdir -p $out/share/ + cp -r dist/*-unpacked/resources $out/share/signal-desktop + + for icon in build/icons/png/* + do + install -Dm644 $icon $out/share/icons/hicolor/`basename ''${icon%.png}`/apps/signal-desktop.png + done + + makeWrapper '${lib.getExe electron}' "$out/bin/signal-desktop" \ + --add-flags "$out/share/signal-desktop/app.asar" \ + --set-default ELECTRON_FORCE_IS_PACKAGED 1 \ + --add-flags "\''${NIXOS_OZONE_WL:+\''${WAYLAND_DISPLAY:+--ozone-platform-hint=auto --enable-features=WaylandWindowDecorations --enable-wayland-ime=true}}" + + runHook postInstall + ''; + + desktopItems = [ + (makeDesktopItem { + name = finalAttrs.pname; + desktopName = "Signal"; + exec = "${finalAttrs.meta.mainProgram} %U"; + type = "Application"; + terminal = false; + icon = "signal-desktop"; + comment = "Private messaging from your desktop"; + startupWMClass = "signal"; + mimeTypes = [ + "x-scheme-handler/sgnl" + "x-scheme-handler/signalcaptcha" + ]; + categories = [ + "Network" + "InstantMessaging" + "Chat" + ]; + }) + ]; + + passthru = { + inherit sqlcipher-signal-extension libsignal-node; + tests.application-launch = nixosTests.signal-desktop; + }; + + meta = { + description = "Private, simple, and secure messenger (nixpkgs build)"; + longDescription = '' + Signal Desktop is an Electron application that links with your + "Signal Android" or "Signal iOS" app. + ''; + homepage = "https://signal.org/"; + changelog = "https://github.com/signalapp/Signal-Desktop/releases/tag/v${finalAttrs.version}"; + license = + with lib.licenses; + [ + agpl3Only + + # Various npm packages + free + ] + ++ lib.optional withAppleEmojis unfree; + maintainers = with lib.maintainers; [ + marcin-serwin + ]; + mainProgram = "signal-desktop"; + platforms = [ + "x86_64-linux" + "aarch64-linux" + ]; + sourceProvenance = with lib.sourceTypes; [ + fromSource + + # ringrtc + binaryNativeCode + ]; + }; +}) diff --git a/pkgs/by-name/si/signal-desktop-source/replace-apple-emoji-with-noto-emoji.patch b/pkgs/by-name/si/signal-desktop-source/replace-apple-emoji-with-noto-emoji.patch new file mode 100644 index 000000000000..3779f5859b23 --- /dev/null +++ b/pkgs/by-name/si/signal-desktop-source/replace-apple-emoji-with-noto-emoji.patch @@ -0,0 +1,129 @@ +diff --git a/ACKNOWLEDGMENTS.md b/ACKNOWLEDGMENTS.md +index aed1048..e4c1f50 100644 +--- a/ACKNOWLEDGMENTS.md ++++ b/ACKNOWLEDGMENTS.md +@@ -745,30 +745,6 @@ Signal Desktop makes use of the following open source projects. + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + +-## emoji-datasource-apple +- +- The MIT License (MIT) +- +- Copyright (c) 2013 Cal Henderson +- +- Permission is hereby granted, free of charge, to any person obtaining a copy +- of this software and associated documentation files (the "Software"), to deal +- in the Software without restriction, including without limitation the rights +- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +- copies of the Software, and to permit persons to whom the Software is +- furnished to do so, subject to the following conditions: +- +- The above copyright notice and this permission notice shall be included in all +- copies or substantial portions of the Software. +- +- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +- SOFTWARE. +- + ## emoji-regex + + Copyright Mathias Bynens +diff --git a/app/protocol_filter.ts b/app/protocol_filter.ts +index 68dceea..4b35bb1 100644 +--- a/app/protocol_filter.ts ++++ b/app/protocol_filter.ts +@@ -59,6 +59,7 @@ function _createFileHandler({ + const allowedRoots = [ + userDataPath, + installPath, ++ "@noto-emoji-pngs@", + getAvatarsPath(userDataPath), + getBadgesPath(userDataPath), + getDraftPath(userDataPath), +diff --git a/package.json b/package.json +index 3a6ac26..40cdb25 100644 +--- a/package.json ++++ b/package.json +@@ -130,7 +130,6 @@ + "dashdash": "2.0.0", + "direction": "1.0.4", + "emoji-datasource": "15.1.2", +- "emoji-datasource-apple": "15.1.2", + "emoji-regex": "10.4.0", + "encoding": "0.1.13", + "fabric": "4.6.0", +diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml +index ba2f205..705e454 100644 +--- a/pnpm-lock.yaml ++++ b/pnpm-lock.yaml +@@ -169,9 +169,6 @@ importers: + emoji-datasource: + specifier: 15.1.2 + version: 15.1.2 +- emoji-datasource-apple: +- specifier: 15.1.2 +- version: 15.1.2 + emoji-regex: + specifier: 10.4.0 + version: 10.4.0 +@@ -4790,9 +4787,6 @@ packages: + resolution: {integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==} + engines: {node: '>=12'} + +- emoji-datasource-apple@15.1.2: +- resolution: {integrity: sha512-32UZTK36x4DlvgD1smkmBlKmmJH7qUr5Qut4U/on2uQLGqNXGbZiheq6/LEA8xRQEUrmNrGEy25wpEI6wvYmTg==} +- + emoji-datasource@15.1.2: + resolution: {integrity: sha512-tXAqGsrDVhgCRpFePtaD9P4Z8Ro2SUQSL/4MIJBG0SxqQJaMslEbin8J53OaFwEBu6e7JxFaIF6s4mw9+8acAQ==} + +@@ -14929,8 +14923,6 @@ snapshots: + + emittery@0.13.1: {} + +- emoji-datasource-apple@15.1.2: {} +- + emoji-datasource@15.1.2: {} + + emoji-regex@10.4.0: {} +diff --git a/ts/components/conversation/Emojify.tsx b/ts/components/conversation/Emojify.tsx +index f0b1115..7613230 100644 +--- a/ts/components/conversation/Emojify.tsx ++++ b/ts/components/conversation/Emojify.tsx +@@ -35,8 +35,15 @@ function getImageTag({ + } + + let srcSet: string | undefined; ++ const emojiToNotoName = (emoji: string): string => ++ `emoji_u${ ++ [...emoji] ++ .filter(c => c != "\ufe0f") ++ .map(c => c.codePointAt(0)?.toString(16).padStart(4, "0")) ++ .join("_") ++ }.png`; + if (sizeClass != null && JUMBO_SIZES.has(sizeClass)) { +- srcSet = `emoji://jumbo?emoji=${encodeURIComponent(match)} 2x, ${img}`; ++ srcSet = `file://@noto-emoji-pngs@/${emojiToNotoName(match)} 2x, ${img}`; + } + + return ( +diff --git a/ts/components/emoji/lib.ts b/ts/components/emoji/lib.ts +index 9753017..cf51d3d 100644 +--- a/ts/components/emoji/lib.ts ++++ b/ts/components/emoji/lib.ts +@@ -102,7 +102,10 @@ const ROOT_PATH = get( + ); + + const makeImagePath = (src: string) => { +- return `${ROOT_PATH}node_modules/emoji-datasource-apple/img/apple/64/${src}`; ++ const datasourceToNoto = (name: string): string => ++ `emoji_u${name.slice(0,-4).split("-").filter(c => c != "fe0f").join("_")}.png`; ++ ++ return `@noto-emoji-pngs@/${datasourceToNoto(src)}`; + }; + + const imageQueue = new PQueue({ diff --git a/pkgs/by-name/si/signal-desktop-source/sqlcipher-signal-extension.nix b/pkgs/by-name/si/signal-desktop-source/sqlcipher-signal-extension.nix new file mode 100644 index 000000000000..e53a3e63eb0a --- /dev/null +++ b/pkgs/by-name/si/signal-desktop-source/sqlcipher-signal-extension.nix @@ -0,0 +1,105 @@ +{ + rustPlatform, + lib, + fetchFromGitHub, + sqlcipher, + fetchpatch, + stdenv, + openssl, + tcl, + buildEnv, + rust-cbindgen, +}: +let + signal-sqlcipher-extension = rustPlatform.buildRustPackage (finalAttrs: { + pname = "signal-sqlcipher-extension"; + version = "0.2.1"; + + src = fetchFromGitHub { + owner = "signalapp"; + repo = "Signal-Sqlcipher-Extension"; + tag = "v${finalAttrs.version}"; + hash = "sha256-INSkm7ZuetPASuIqezzzG/bXoEHClUb9XpxWbxLVXRc="; + }; + useFetchCargoVendor = true; + cargoHash = "sha256-qT4HM/FRL8qugKKNlMYM/0zgUsC6cDOa9fgd1d4VIrc="; + + meta = { + description = "SQLite extension used by Signal Desktop"; + homepage = "https://github.com/signalapp/Signal-Sqlcipher-Extension"; + license = lib.licenses.agpl3Only; + maintainers = with lib.maintainers; [ marcin-serwin ]; + platforms = lib.platforms.all; + }; + }); + + sqlcipher-amalgamation = stdenv.mkDerivation { + pname = "sqlcipher-with-signal-extension"; + + inherit (sqlcipher) version src meta; + + patches = [ + (fetchpatch { + # https://github.com/sqlcipher/sqlcipher/pull/529 + name = "custom-crypto-provider.patch"; + url = "https://github.com/sqlcipher/sqlcipher/commit/0e3b20c155df8a2943b62a9f3cc0f4d3dba9e152.patch"; + hash = "sha256-OKh6qCGHBQWZyzXfyEveAs71wrNwlWLuG9jNqDeKNG4="; + }) + ]; + + nativeBuildInputs = [ tcl ]; + + buildInputs = [ openssl ]; + + CFLAGS = [ "-DSQLITE_ENABLE_UPDATE_DELETE_LIMIT=1" ]; + + makeFlags = [ "sqlite3.c" ]; + + installPhase = '' + install -Dm644 sqlite3.c $out/src/sqlite3.c + install -Dm644 sqlite3.h $out/include/sqlite3.h + install -Dm644 sqlite3ext.h $out/include/sqlite3ext.h + ''; + }; + + signal-tokenizer-headers = rustPlatform.buildRustPackage (finalAttrs: { + pname = "Signal-FTS5-Extension"; + version = "0.2.1"; + + src = fetchFromGitHub { + owner = "signalapp"; + repo = "Signal-FTS5-Extension"; + tag = "v${finalAttrs.version}"; + hash = "sha256-MzgdRuRsfL3yhlVU0RAAUtAaOukMpqSSa42nRYhpmh0="; + }; + useFetchCargoVendor = true; + cargoHash = "sha256-0DDX3ciXk5/3MqsHzxV8s4qEhqYmrwGg7cSbrkFRZbw="; + + nativeBuildInputs = [ rust-cbindgen ]; + + buildPhase = '' + cbindgen --profile release . -o signal-tokenizer.h + ''; + installPhase = '' + install -Dm644 signal-tokenizer.h $out/include/signal-tokenizer.h + ''; + doCheck = false; + }); + +in +buildEnv { + name = "sqlcipher-signal"; + + paths = [ + sqlcipher-amalgamation + signal-tokenizer-headers + signal-sqlcipher-extension + ]; + + postBuild = '' + install -Dm644 ${./sqlite3.gyp} $out/share/sqlite3.gyp + substituteInPlace $out/share/sqlite3.gyp \ + --replace-fail "@extension@" "$out" \ + --replace-fail "@static_lib_ext@" "${stdenv.hostPlatform.extensions.staticLibrary}" + ''; +} diff --git a/pkgs/by-name/si/signal-desktop-source/sqlite3.gyp b/pkgs/by-name/si/signal-desktop-source/sqlite3.gyp new file mode 100644 index 000000000000..2c73ab250b61 --- /dev/null +++ b/pkgs/by-name/si/signal-desktop-source/sqlite3.gyp @@ -0,0 +1,40 @@ +{ + 'includes': ['common.gypi'], + 'targets': [ + { + 'target_name': 'locate_sqlite3', + 'type': 'none', + 'copies': [{ + 'files': [ + '@extension@/src/sqlite3.c', + ], + 'destination': '<(SHARED_INTERMEDIATE_DIR)/sqlite3', + }], + }, + { + 'target_name': 'sqlite3', + 'type': 'static_library', + 'dependencies': ['locate_sqlite3'], + 'sources': ['<(SHARED_INTERMEDIATE_DIR)/sqlite3/sqlite3.c'], + 'include_dirs': [ + '<(SHARED_INTERMEDIATE_DIR)/sqlite3/', + ], + 'direct_dependent_settings': { + 'include_dirs': [ + '@extension@/include', + ], + }, + 'cflags': ['-std=c99', '-w'], + 'xcode_settings': { + 'OTHER_CFLAGS': ['-std=c99'], + 'WARNING_CFLAGS': ['-w'], + }, + 'includes': ['defines.gypi'], + 'link_settings': { + 'libraries': [ + '@extension@/lib/libsignal_sqlcipher_extension@static_lib_ext@', + ] + } + }, + ], +}