Compare commits

..

9 commits

21 changed files with 1098 additions and 457 deletions

View file

@ -143,6 +143,27 @@
"type": "github"
}
},
"crane_2": {
"inputs": {
"nixpkgs": [
"unbound-rust-mod",
"nixpkgs"
]
},
"locked": {
"lastModified": 1722960479,
"narHash": "sha256-NhCkJJQhD5GUib8zN9JrmYGMwt4lCRp6ZVNzIiYCl0Y=",
"owner": "ipetkov",
"repo": "crane",
"rev": "4c6c77920b8d44cd6660c1621dea6b3fc4b4c4f4",
"type": "github"
},
"original": {
"owner": "ipetkov",
"repo": "crane",
"type": "github"
}
},
"disko": {
"inputs": {
"nixpkgs": [
@ -324,16 +345,15 @@
]
},
"locked": {
"lastModified": 1714645000,
"narHash": "sha256-RelIgcYWJnUdE96Hjh8gR6wmsNgLc1LHDZY5VtdR6Eo=",
"lastModified": 1723599998,
"narHash": "sha256-9a/Dq7WUhP35WrpqXUpDBsnKoKASsYB8FQjIeVlghm4=",
"owner": "chayleaf",
"repo": "home-manager",
"rev": "2a477b78f5e95a0b8ed741786524ca0fbfe0c230",
"rev": "c29f1c444bbfe3cfa22e347923fe04245030bcda",
"type": "github"
},
"original": {
"owner": "chayleaf",
"ref": "librewolf",
"repo": "home-manager",
"type": "github"
}
@ -757,7 +777,8 @@
"notnft": "notnft",
"nur": "nur",
"osu-wine": "osu-wine",
"rust-overlay": "rust-overlay"
"rust-overlay": "rust-overlay",
"unbound-rust-mod": "unbound-rust-mod"
}
},
"rust-overlay": {
@ -908,6 +929,27 @@
"type": "github"
}
},
"unbound-rust-mod": {
"inputs": {
"crane": "crane_2",
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1723550937,
"narHash": "sha256-5gEXPy9qIrNi0nPk3D1ctVxh5Ndfrhyu+02R6BIcnHQ=",
"ref": "refs/heads/master",
"rev": "e0f642180056f351fd712f2b4f3149fd9f02367f",
"revCount": 24,
"type": "git",
"url": "https://git.pavluk.org/chayleaf/unbound-rust-mod.git"
},
"original": {
"type": "git",
"url": "https://git.pavluk.org/chayleaf/unbound-rust-mod.git"
}
},
"utils": {
"inputs": {
"systems": "systems_2"

View file

@ -29,7 +29,7 @@
inputs.nixpkgs.follows = "nixpkgs";
};
home-manager = {
url = "github:chayleaf/home-manager/librewolf";
url = "github:chayleaf/home-manager";
inputs.nixpkgs.follows = "nixpkgs";
};
nix-gaming = {
@ -52,6 +52,10 @@
url = "gitlab:simple-nixos-mailserver/nixos-mailserver";
inputs.nixpkgs.follows = "nixpkgs";
};
unbound-rust-mod = {
url = "git+https://git.pavluk.org/chayleaf/unbound-rust-mod.git";
inputs.nixpkgs.follows = "nixpkgs";
};
flake-compat = {
url = "github:edolstra/flake-compat";
flake = false;
@ -69,6 +73,7 @@
# nixos-router = true;
# notnft = true;
# nixpkgs = true;
# unbound-rust-mod = true;
};
# IRL-related stuff I'd rather not put into git
priv =
@ -197,9 +202,11 @@
hardware = inputs.nixos-hardware.nixosModules;
} // args.specialArgs or { };
modules = [
{ _module.args = {
pkgs-kernel = import inputs.nixpkgs-kernel { inherit (args) system; overlays = all-overlays; };
}; }
({ config, ... }: {
_module.args = {
pkgs-kernel = import inputs.nixpkgs-kernel { inherit (args) system; overlays = all-overlays ++ config.nixpkgs.overlays; };
};
})
(getPrivSys hostname)
{ networking.hostName = lib.mkDefault hostname;
nixpkgs.overlays = all-overlays; }
@ -238,7 +245,11 @@
extraSpecialArgs = { inherit inputs; };
modules = [
./home/hosts/remote.nix
({ pkgs, ... }: { home.file.hysteria.source = pkgs.hysteria; })
({ pkgs, ... }: {
home.file.hysteria.source = pkgs.hysteria;
home.file.shadowsocks-libev.source = pkgs.shadowsocks-libev;
home.file.shadowsocks-rust.source = pkgs.shadowsocks-rust;
})
];
};
}
@ -261,7 +272,6 @@
++ [
(getPrivUser hostname user)
({ pkgs, lib, ... }: {
nixpkgs.overlays = all-overlays;
nix.package = lib.mkDefault pkgs.nixForNixPlugins;
})
];

View file

@ -81,6 +81,7 @@
lalrpop
tio
tdesktop
osu-wine
];
xdg.configFile."looking-glass/client.ini".text = ''
[app]

View file

@ -7,7 +7,7 @@
{
imports = [ ./gui.nix ];
programs.firefox = {
programs.librewolf = {
enable = true;
package = pkgs.wrapFirefox pkgs.librewolf-unwrapped {
inherit (pkgs.librewolf-unwrapped) extraPrefsFiles extraPoliciesFiles;
@ -15,6 +15,17 @@
libName = "librewolf";
nativeMessagingHosts = with pkgs; [ keepassxc ];
};
profiles.other.id = 1;
profiles.other.bookmarks = [{
name = "bookmarklets";
toolbar = true;
bookmarks = [
{
name = "example.com";
url = "https://example.com";
}
];
}];
profiles.chayleaf = lib.mkMerge [
{
extensions = (with config.nur.repos.rycee.firefox-addons; [

View file

@ -172,11 +172,11 @@ commonConfig = {
names = [ "Noto Sans Mono" "Symbols Nerd Font Mono" ];
size = 16.0;
};
gaps = {
smartBorders = "on";
smartGaps = true;
inner = 10;
};
# gaps = {
# smartBorders = "on";
# smartGaps = true;
# inner = 10;
# };
window.hideEdgeBorders = "smart";
workspaceAutoBackAndForth = true;
};
@ -259,15 +259,25 @@ in
'';
wayland.windowManager.sway = {
wrapperFeatures.gtk = true;
package = pkgs.sway-unwrapped.overrideAttrs (old: {
package = let
cfg = config.wayland.windowManager.sway;
in pkgs.sway.override {
sway-unwrapped = pkgs.sway-unwrapped.overrideAttrs (old: {
patches = old.patches or [] ++ [
./sway.patch
../../pkgs/sway/allow-other.patch
/*(pkgs.fetchpatch {
url = "https://patch-diff.githubusercontent.com/raw/swaywm/sway/pull/6920.patch";
sha256 = "sha256-XgkysduhHbmprE334yeL65txpK0HNXeCmgCZMxpwsgU=";
})*/
];
] ++ lib.optionals config.phone.enable
(map
(x: ../../pkgs/sway/${x})
(builtins.filter (lib.hasInfix "-mobile-") (builtins.attrNames (builtins.readDir ../../pkgs/sway))));
});
inherit (cfg) extraSessionCommands extraOptions;
withBaseWrapper = cfg.wrapperFeatures.base;
withGtkWrapper = cfg.wrapperFeatures.gtk;
};
extraConfig = ''
title_align center
'';
@ -297,6 +307,11 @@ in
app_id = "^org.keepassxc.KeePassXC$";
title = "^KeePassXC - (?:Browser |)?(?:Access Request|)$";
}; }
{ command = "floating on; border normal";
criteria = {
class = "ghidra-Ghidra";
title = "\\[CodeBrowser.*\\]$";
}; }
]; };
assigns = {
"2" = [

View file

@ -19,8 +19,10 @@
sha256 = "sha256-zH4hbQ8+9TYRVW/XYqmAVsi0vsSPn1LPqXxr0gi0j1E=";
};
});*/
settings = lib.toList {
layer = "top";
settings = let
layer = if config.phone.enable then "overlay" else "top";
in lib.toList {
inherit layer;
position = "top";
ipc = true;
height = 40;
@ -151,7 +153,7 @@
};
} ++ lib.optionals config.phone.enable [
{
layer = "top";
inherit layer;
position = "top";
ipc = true;
height = 40;
@ -168,7 +170,7 @@
modules-right = [ "clock" ];
}
{
layer = "top";
inherit layer;
position = "bottom";
ipc = true;
height = 80;

View file

@ -22,6 +22,8 @@ in
inherit (inputs.osu-wine.packages.${pkgs.system}) osu-wine;
matrix-appservice-discord = pkgs.callPackage ./matrix-appservice-discord { inherit (pkgs) matrix-appservice-discord; };
krita = pkgs.callPackage ./krita { inherit (pkgs) krita; };
openssh = pkgs.openssh.overrideAttrs (old: rec {
version = "9.8p1";
src = pkgs.fetchurl {
@ -30,6 +32,11 @@ in
};
});
inherit (inputs.unbound-rust-mod.packages.${pkgs.system}) unbound-mod;
unbound-full = pkgs.unbound-full.overrideAttrs (old: {
configureFlags = old.configureFlags ++ [ "--with-dynlibmodule" ];
});
buffyboard = pkgs.callPackage ./buffyboard { };
clang-tools_latest = pkgs.clang-tools_16;
clang_latest = pkgs.clang_16;
@ -39,8 +46,12 @@ in
gimp = callPackage ./gimp { inherit (pkgs) gimp; };
home-daemon = callPackage ./home-daemon { };
# pin version
looking-glass-client = pkgs.looking-glass-client.overrideAttrs (old: {
looking-glass-client = pkgs.looking-glass-client.overrideAttrs (old: rec {
version = "B6";
postUnpack = ''
echo ${src.rev} > source/VERSION
export sourceRoot="source/client"
'';
src = pkgs.fetchFromGitHub {
owner = "gnif";
repo = "LookingGlass";
@ -50,8 +61,7 @@ in
};
patches = [ ];
});
kvmfrOverlay = kvmfr: kvmfr.overrideAttrs (old: {
inherit (pkgs'.looking-glass-client) version src;
kvmfrOverlay = kvmfr: (kvmfr.override { inherit (pkgs') looking-glass-client; }).overrideAttrs (old: {
patches = [ ./looking-glass.patch ];
});
mobile-config-firefox = callPackage ./mobile-config-firefox { };

12
pkgs/krita/default.nix Normal file
View file

@ -0,0 +1,12 @@
{ krita, python3Packages }:
(krita.override {
unwrapped = krita.unwrapped.overrideAttrs (old: { patches = old.patches or [] ++ [
./painting-api.patch
./fix-painting-api-crashes.patch
./painting-api-options.patch
./painting-api-pressure.patch
]; });
}).overrideAttrs (old: {
buildInputs = old.buildInputs ++ [ python3Packages.requests ];
})

View file

@ -0,0 +1,145 @@
diff --git a/libs/libkis/Node.cpp b/libs/libkis/Node.cpp
index 84f24d3..0e2370e 100644
--- a/libs/libkis/Node.cpp
+++ b/libs/libkis/Node.cpp
@@ -76,6 +76,8 @@
#include "KisAsynchronousStrokeUpdateHelper.h"
#include "kis_stroke_strategy.h"
#include "PaintingResources.h"
+#include "KisMainWindow.h"
+#include "kis_canvas2.h"
struct Node::Private {
@@ -833,8 +835,48 @@ KisNodeSP Node::node() const
return d->node;
}
+QString Node::paintAbility()
+{
+ // Taken from KisTool:nodePaintAbility().
+ KisMainWindow *mainWindow = KisPart::instance()->currentMainwindow();
+ KisCanvas2 *canvas = mainWindow->activeView()->canvasBase();
+ if (canvas->resourceManager()->resource(KoCanvasResource::CurrentPaintOpPreset).isNull()) {
+ return "UNPAINTABLE";
+ }
+
+ if (!d->node) {
+ return "UNPAINTABLE";
+ }
+
+ if (d->node->inherits("KisShapeLayer")) {
+ return "VECTOR";
+ }
+ if (d->node->inherits("KisCloneLayer")) {
+ return "CLONE";
+ }
+ if (d->node->paintDevice()) {
+
+ KisPaintOpPresetSP currentPaintOpPreset = canvas->resourceManager()->resource(KoCanvasResource::CurrentPaintOpPreset).value<KisPaintOpPresetSP>();
+ if (currentPaintOpPreset->paintOp().id() == "mypaintbrush") {
+ const KoColorSpace *colorSpace = d->node->paintDevice()->colorSpace();
+ if (colorSpace->colorModelId() != RGBAColorModelID) {
+ return "MYPAINTBRUSH_UNPAINTABLE";
+ }
+ }
+
+ return "PAINT";
+ }
+
+ return "UNPAINTABLE";
+}
+
void Node::paintLine(const QPointF pointOne, const QPointF pointTwo)
{
+ if (paintAbility() != "PAINT") {
+ dbgScript << "Script attempted to use Node::paintLine() on an unpaintable node, ignoring.";
+ return;
+ }
+
KisPaintInformation pointOneInfo;
pointOneInfo.setPressure(1.0);
pointOneInfo.setPos(pointOne);
@@ -850,6 +892,11 @@ void Node::paintLine(const QPointF pointOne, const QPointF pointTwo)
void Node::paintRectangle(const QRectF &rect)
{
+ if (paintAbility() != "PAINT") {
+ dbgScript << "Script attempted to use Node::paintRectangle() on an unpaintable node, ignoring.";
+ return;
+ }
+
// reference class where this stuff is being done. Maybe can use the "facade" like that does for setup?
// void KisFigurePaintingToolHelper::paintRect(const QRectF &rect)
@@ -860,6 +907,11 @@ void Node::paintRectangle(const QRectF &rect)
void Node::paintPolygon(const QList<QPointF> listPoint)
{
+ if (paintAbility() != "PAINT") {
+ dbgScript << "Script attempted to use Node::paintPolygon() on an unpaintable node, ignoring.";
+ return;
+ }
+
// strategy needs points in vPointF format
QVector<QPointF> points = points.fromList(listPoint);
KisFigurePaintingToolHelper helper = PaintingResources::createHelper(d->image);
@@ -869,6 +921,11 @@ void Node::paintPolygon(const QList<QPointF> listPoint)
void Node::paintEllipse(const QRectF &rect)
{
+ if (paintAbility() != "PAINT") {
+ dbgScript << "Script attempted to use Node::paintEllipse() on an unpaintable node, ignoring.";
+ return;
+ }
+
KisFigurePaintingToolHelper helper = PaintingResources::createHelper(d->image);
helper.paintEllipse(rect);
}
@@ -876,6 +933,11 @@ void Node::paintEllipse(const QRectF &rect)
void Node::paintPath(const QPainterPath &path)
{
+ if (paintAbility() != "PAINT") {
+ dbgScript << "Script attempted to use Node::paintPath() on an unpaintable node, ignoring.";
+ return;
+ }
+
KisFigurePaintingToolHelper helper = PaintingResources::createHelper(d->image);
helper.paintPainterPath(path);
}
diff --git a/libs/libkis/Node.h b/libs/libkis/Node.h
index 10daba4..89a6f40 100644
--- a/libs/libkis/Node.h
+++ b/libs/libkis/Node.h
@@ -601,6 +601,18 @@ public Q_SLOTS:
*/
void paintPath(const QPainterPath &path);
+ /**
+ * @brief paintAbility can be used to determine whether this node can be painted on with the current brush preset.
+ * @return QString, one of the following:
+ * <ul>
+ * <li>VECTOR - This node is vector-based.</li>
+ * <li>CLONE - This node is a Clone Layer.</li>
+ * <li>PAINT - This node is paintable by the current brush preset.</li>
+ * <li>UNPAINTABLE - This node is not paintable, or a null preset is somehow selected./li>
+ * <li>MYPAINTBRUSH_UNPAINTABLE - This node's non-RGBA colorspace cannot be painted on by the currently selected MyPaint brush.</li>
+ */
+ QString paintAbility();
+
private:
friend class Filter;
diff --git a/plugins/extensions/pykrita/sip/krita/Node.sip b/plugins/extensions/pykrita/sip/krita/Node.sip
index cbcef0f..6270bd9 100644
--- a/plugins/extensions/pykrita/sip/krita/Node.sip
+++ b/plugins/extensions/pykrita/sip/krita/Node.sip
@@ -75,6 +75,7 @@ public Q_SLOTS:
void paintPolygon(const QList<QPointF> points);
void paintEllipse(const QRectF &rect);
void paintPath(const QPainterPath &path);
+ QString paintAbility();
Q_SIGNALS:
private:
};

View file

@ -0,0 +1,332 @@
diff --git a/libs/libkis/Node.cpp b/libs/libkis/Node.cpp
index 0e2370e..3eed4ea 100644
--- a/libs/libkis/Node.cpp
+++ b/libs/libkis/Node.cpp
@@ -870,7 +870,7 @@ QString Node::paintAbility()
return "UNPAINTABLE";
}
-void Node::paintLine(const QPointF pointOne, const QPointF pointTwo)
+void Node::paintLine(const QPointF pointOne, const QPointF pointTwo, const QString strokeStyle)
{
if (paintAbility() != "PAINT") {
dbgScript << "Script attempted to use Node::paintLine() on an unpaintable node, ignoring.";
@@ -885,12 +885,12 @@ void Node::paintLine(const QPointF pointOne, const QPointF pointTwo)
pointTwoInfo.setPressure(1.0);
pointTwoInfo.setPos(pointTwo);
- KisFigurePaintingToolHelper helper = PaintingResources::createHelper(d->image);
+ KisFigurePaintingToolHelper helper = PaintingResources::createHelper(d->image, strokeStyle);
helper.paintLine(pointOneInfo, pointTwoInfo);
}
-void Node::paintRectangle(const QRectF &rect)
+void Node::paintRectangle(const QRectF &rect, const QString strokeStyle, const QString fillStyle)
{
if (paintAbility() != "PAINT") {
dbgScript << "Script attempted to use Node::paintRectangle() on an unpaintable node, ignoring.";
@@ -900,12 +900,12 @@ void Node::paintRectangle(const QRectF &rect)
// reference class where this stuff is being done. Maybe can use the "facade" like that does for setup?
// void KisFigurePaintingToolHelper::paintRect(const QRectF &rect)
- KisFigurePaintingToolHelper helper = PaintingResources::createHelper(d->image);
+ KisFigurePaintingToolHelper helper = PaintingResources::createHelper(d->image, strokeStyle, fillStyle);
helper.paintRect(rect);
}
-void Node::paintPolygon(const QList<QPointF> listPoint)
+void Node::paintPolygon(const QList<QPointF> listPoint, const QString strokeStyle, const QString fillStyle)
{
if (paintAbility() != "PAINT") {
dbgScript << "Script attempted to use Node::paintPolygon() on an unpaintable node, ignoring.";
@@ -914,30 +914,30 @@ void Node::paintPolygon(const QList<QPointF> listPoint)
// strategy needs points in vPointF format
QVector<QPointF> points = points.fromList(listPoint);
- KisFigurePaintingToolHelper helper = PaintingResources::createHelper(d->image);
+ KisFigurePaintingToolHelper helper = PaintingResources::createHelper(d->image, strokeStyle, fillStyle);
helper.paintPolygon(points);
}
-void Node::paintEllipse(const QRectF &rect)
+void Node::paintEllipse(const QRectF &rect, const QString strokeStyle, const QString fillStyle)
{
if (paintAbility() != "PAINT") {
dbgScript << "Script attempted to use Node::paintEllipse() on an unpaintable node, ignoring.";
return;
}
- KisFigurePaintingToolHelper helper = PaintingResources::createHelper(d->image);
+ KisFigurePaintingToolHelper helper = PaintingResources::createHelper(d->image, strokeStyle, fillStyle);
helper.paintEllipse(rect);
}
-void Node::paintPath(const QPainterPath &path)
+void Node::paintPath(const QPainterPath &path, const QString strokeStyle, const QString fillStyle)
{
if (paintAbility() != "PAINT") {
dbgScript << "Script attempted to use Node::paintPath() on an unpaintable node, ignoring.";
return;
}
- KisFigurePaintingToolHelper helper = PaintingResources::createHelper(d->image);
+ KisFigurePaintingToolHelper helper = PaintingResources::createHelper(d->image, strokeStyle, fillStyle);
helper.paintPainterPath(path);
}
diff --git a/libs/libkis/Node.h b/libs/libkis/Node.h
index 89a6f40..ecf9845 100644
--- a/libs/libkis/Node.h
+++ b/libs/libkis/Node.h
@@ -13,6 +13,8 @@
#include "kritalibkis_export.h"
#include "libkis.h"
+#include "PaintingResources.h"
+
/**
* Node represents a layer or mask in a Krita image's Node hierarchy. Group layers can contain
* other layers and masks; layers can contain masks.
@@ -574,33 +576,101 @@ public Q_SLOTS:
* @brief paint a line on the canvas. Uses current brush preset
* @param pointOne starting point
* @param pointTwo end point
+ * @param strokeStyle appearance of the outline, one of:
+ * <ul>
+ * <li>None - will use Foreground Color, since line would be invisible otherwise
+ * <li>ForegroundColor</li>
+ * <li>BackgroundColor</li>
+ * </ul>
*/
- void paintLine(const QPointF pointOne, const QPointF pointTwo);
+ void paintLine(const QPointF pointOne, const QPointF pointTwo, const QString strokeStyle = PaintingResources::defaultStrokeStyle);
/**
* @brief paint a rectangle on the canvas. Uses current brush preset
* @param rect QRect with x, y, width, and height
+ * @param strokeStyle appearance of the outline, one of:
+ * <ul>
+ * <li>None
+ * <li>ForegroundColor</li>
+ * <li>BackgroundColor</li>
+ * </ul>
+ * Default is ForegroundColor.
+ * @param fillStyle appearance of the fill, one of:
+ * <ul>
+ * <li>None
+ * <li>ForegroundColor</li>
+ * <li>BackgroundColor</li>
+ * <li>Pattern</li>
+ * </ul>
+ * Default is None.
*/
- void paintRectangle(const QRectF &rect);
+ void paintRectangle(const QRectF &rect,
+ const QString strokeStyle = PaintingResources::defaultStrokeStyle,
+ const QString fillStyle = PaintingResources::defaultFillStyle);
/**
* @brief paint a polygon on the canvas. Uses current brush preset
* @param list of Qpoints
+ * <ul>
+ * <li>None
+ * <li>ForegroundColor</li>
+ * <li>BackgroundColor</li>
+ * </ul>
+ * Default is ForegroundColor.
+ * @param fillStyle appearance of the fill, one of:
+ * <ul>
+ * <li>None
+ * <li>ForegroundColor</li>
+ * <li>BackgroundColor</li>
+ * <li>Pattern</li>
+ * </ul>
+ * Default is None.
*/
- void paintPolygon(const QList<QPointF> points);
-
+ void paintPolygon(const QList<QPointF> points,
+ const QString strokeStyle = PaintingResources::defaultStrokeStyle,
+ const QString fillStyle = PaintingResources::defaultFillStyle);
/**
* @brief paint an ellipse on the canvas. Uses current brush preset
* @param rect QRect with x, y, width, and height
+ * <ul>
+ * <li>None
+ * <li>ForegroundColor</li>
+ * <li>BackgroundColor</li>
+ * </ul>
+ * Default is ForegroundColor.
+ * @param fillStyle appearance of the fill, one of:
+ * <ul>
+ * <li>None
+ * <li>ForegroundColor</li>
+ * <li>BackgroundColor</li>
+ * <li>Pattern</li>
+ * </ul>
+ * Default is None.
*/
- void paintEllipse(const QRectF &rect);
-
+ void paintEllipse(const QRectF &rect,
+ const QString strokeStyle = PaintingResources::defaultStrokeStyle,
+ const QString fillStyle = PaintingResources::defaultFillStyle);
/**
* @brief paint a custom path on the canvas. Uses current brush preset
* @param path QPainterPath to determine path
+ * <ul>
+ * <li>None
+ * <li>ForegroundColor</li>
+ * <li>BackgroundColor</li>
+ * </ul>
+ * Default is ForegroundColor.
+ * @param fillStyle appearance of the fill, one of:
+ * <ul>
+ * <li>None
+ * <li>ForegroundColor</li>
+ * <li>BackgroundColor</li>
+ * <li>Pattern</li>
+ * </ul>
+ * Default is None.
*/
- void paintPath(const QPainterPath &path);
-
+ void paintPath(const QPainterPath &path,
+ const QString strokeStyle = PaintingResources::defaultStrokeStyle,
+ const QString fillStyle = PaintingResources::defaultFillStyle);
/**
* @brief paintAbility can be used to determine whether this node can be painted on with the current brush preset.
* @return QString, one of the following:
@@ -610,6 +680,7 @@ public Q_SLOTS:
* <li>PAINT - This node is paintable by the current brush preset.</li>
* <li>UNPAINTABLE - This node is not paintable, or a null preset is somehow selected./li>
* <li>MYPAINTBRUSH_UNPAINTABLE - This node's non-RGBA colorspace cannot be painted on by the currently selected MyPaint brush.</li>
+ * </ul>
*/
QString paintAbility();
diff --git a/libs/libkis/PaintingResources.cpp b/libs/libkis/PaintingResources.cpp
index 62abb26..58d67ae 100644
--- a/libs/libkis/PaintingResources.cpp
+++ b/libs/libkis/PaintingResources.cpp
@@ -53,9 +53,28 @@
#include "kis_painting_information_builder.h"
#include "KisAsynchronousStrokeUpdateHelper.h"
#include "kis_stroke_strategy.h"
-
-
-KisFigurePaintingToolHelper PaintingResources::createHelper(KisImageWSP image)
+#include "KisViewManager.h"
+#include "KisMainWindow.h"
+#include "kis_image.h"
+#include "KisToolShapeUtils.h"
+
+
+const QStringList StrokeStyle = {
+ "None", // 0 = KisToolShapeUtils::StrokeStyle::StrokeStyleNone
+ "ForegroundColor", // KisToolShapeUtils::StrokeStyle::StrokeStyleForeground
+ "BackgroundColor" // KisToolShapeUtils::StrokeStyle::StrokeStyleBackground
+};
+
+const QStringList FillStyle = {
+ "None", // 0 = KisToolShapeUtils::FillStyle::FillStyleNone
+ "ForegroundColor", // KisToolShapeUtils::FillStyle::FillStyleForegroundColor
+ "BackgroundColor", // KisToolShapeUtils::FillStyle::FillStyleBackgroundColor
+ "Pattern" // KisToolShapeUtils::FillStyle::FillStylePattern
+};
+
+KisFigurePaintingToolHelper PaintingResources::createHelper(KisImageWSP image,
+ const QString strokeStyleString,
+ const QString fillStyleString)
{
// need to grab the resource provider
KisView *activeView = KisPart::instance()->currentMainwindow()->activeView();
@@ -64,13 +83,35 @@ KisFigurePaintingToolHelper PaintingResources::createHelper(KisImageWSP image)
// grab the image and current layer
KisNodeSP node = activeView->currentNode();
- const KUndo2MagicString name = kundo2_noi18n("python_stroke");
+ int strokeIndex = StrokeStyle.indexOf(strokeStyleString);
+ if (strokeIndex == -1) {
+ dbgScript << "Script tried to paint with invalid strokeStyle" << strokeStyleString << ", ignoring and using" << defaultStrokeStyle << ".";
+ strokeIndex = StrokeStyle.indexOf(defaultStrokeStyle);
+ if (strokeIndex == -1) {
+ warnScript << "PaintingResources::createHelper(): defaultStrokeStyle" << defaultStrokeStyle << "is invalid!";
+ strokeIndex = 1;
+ }
+ }
+ KisToolShapeUtils::StrokeStyle strokeStyle = (KisToolShapeUtils::StrokeStyle) strokeIndex;
+
+ int fillIndex = FillStyle.indexOf(fillStyleString);
+ if (fillIndex == -1) {
+ dbgScript << "Script tried to paint with invalid fillStyle" << fillStyleString << ", ignoring and using" << defaultFillStyle << ".";
+ fillIndex = FillStyle.indexOf(defaultFillStyle);
+ if (fillIndex == -1) {
+ warnScript << "PaintingResources::createHelper(): defaultFillStyle" << defaultFillStyle << " is invalid!";
+ fillIndex = 0;
+ }
+ }
+ KisToolShapeUtils::FillStyle fillStyle = (KisToolShapeUtils::FillStyle) fillIndex;
+
+ const KUndo2MagicString name = kundo2_i18n("Scripted Brush Stroke");
KisFigurePaintingToolHelper helper(
name,
image,
node, resourceManager,
- KisToolShapeUtils::StrokeStyle::StrokeStyleForeground,
- KisToolShapeUtils::FillStyle::FillStyleNone
+ strokeStyle,
+ fillStyle
);
return helper;
diff --git a/libs/libkis/PaintingResources.h b/libs/libkis/PaintingResources.h
index 19bb0d4..174057a 100644
--- a/libs/libkis/PaintingResources.h
+++ b/libs/libkis/PaintingResources.h
@@ -35,12 +35,19 @@
/**
* @brief The PaintingResources namespace
* Sets up information related to making painting strokes.
- * Used primarily in the Document class
+ * Used primarily in the Node class
*
*/
namespace PaintingResources
{
- KisFigurePaintingToolHelper createHelper(KisImageWSP image);
+ // These are set in Node.sip
+ const QString defaultStrokeStyle = "ForegroundColor";
+ const QString defaultFillStyle = "None";
+
+ KisFigurePaintingToolHelper createHelper(KisImageWSP image,
+ const QString strokeStyle = defaultStrokeStyle,
+ const QString fillStyle = defaultFillStyle);
+
};
#endif // LIBKIS_PAINTINGRESOURCES_H
diff --git a/plugins/extensions/pykrita/sip/krita/Node.sip b/plugins/extensions/pykrita/sip/krita/Node.sip
index 6270bd9..884e615 100644
--- a/plugins/extensions/pykrita/sip/krita/Node.sip
+++ b/plugins/extensions/pykrita/sip/krita/Node.sip
@@ -70,11 +70,11 @@ public Q_SLOTS:
int index() const;
QUuid uniqueId() const;
- void paintLine(const QPoint pointOne, const QPoint pointTwo);
- void paintRectangle(const QRectF &rect);
- void paintPolygon(const QList<QPointF> points);
- void paintEllipse(const QRectF &rect);
- void paintPath(const QPainterPath &path);
+ void paintLine(const QPoint pointOne, const QPoint pointTwo, const QString strokeStyle = "ForegroundColor");
+ void paintRectangle(const QRectF &rect, const QString strokeStyle = "ForegroundColor", const QString fillStyle = "None");
+ void paintPolygon(const QList<QPointF> points, const QString strokeStyle = "ForegroundColor", const QString fillStyle = "None");
+ void paintEllipse(const QRectF &rect, const QString strokeStyle = "ForegroundColor", const QString fillStyle = "None");
+ void paintPath(const QPainterPath &path, const QString strokeStyle = "ForegroundColor", const QString fillStyle = "None");
QString paintAbility();
Q_SIGNALS:
private:

View file

@ -0,0 +1,66 @@
diff --git a/libs/libkis/Node.cpp b/libs/libkis/Node.cpp
index 7e15b58c2c..a187c18aad 100644
--- a/libs/libkis/Node.cpp
+++ b/libs/libkis/Node.cpp
@@ -863,7 +863,7 @@ QString Node::paintAbility()
return "UNPAINTABLE";
}
-void Node::paintLine(const QPointF pointOne, const QPointF pointTwo, const QString strokeStyle)
+void Node::paintLine(const QPointF pointOne, const QPointF pointTwo, double pressureOne, double pressureTwo, const QString strokeStyle)
{
if (paintAbility() != "PAINT") {
dbgScript << "Script attempted to use Node::paintLine() on an unpaintable node, ignoring.";
@@ -871,11 +871,11 @@ void Node::paintLine(const QPointF pointOne, const QPointF pointTwo, const QStri
}
KisPaintInformation pointOneInfo;
- pointOneInfo.setPressure(1.0);
+ pointOneInfo.setPressure(pressureOne);
pointOneInfo.setPos(pointOne);
KisPaintInformation pointTwoInfo;
- pointTwoInfo.setPressure(1.0);
+ pointTwoInfo.setPressure(pressureTwo);
pointTwoInfo.setPos(pointTwo);
KisFigurePaintingToolHelper helper = PaintingResources::createHelper(d->image, strokeStyle);
diff --git a/libs/libkis/Node.h b/libs/libkis/Node.h
index ecf9845a22..ce63d3f1bc 100644
--- a/libs/libkis/Node.h
+++ b/libs/libkis/Node.h
@@ -576,6 +576,8 @@ public Q_SLOTS:
* @brief paint a line on the canvas. Uses current brush preset
* @param pointOne starting point
* @param pointTwo end point
+ * @param pressureOne starting pressure
+ * @param pressureTwo end pressure
* @param strokeStyle appearance of the outline, one of:
* <ul>
* <li>None - will use Foreground Color, since line would be invisible otherwise
@@ -583,7 +585,11 @@ public Q_SLOTS:
* <li>BackgroundColor</li>
* </ul>
*/
- void paintLine(const QPointF pointOne, const QPointF pointTwo, const QString strokeStyle = PaintingResources::defaultStrokeStyle);
+ void paintLine(const QPointF pointOne,
+ const QPointF pointTwo,
+ double pressureOne = 1.0,
+ double pressureTwo = 1.0,
+ const QString strokeStyle = PaintingResources::defaultStrokeStyle);
/**
* @brief paint a rectangle on the canvas. Uses current brush preset
diff --git a/plugins/extensions/pykrita/sip/krita/Node.sip b/plugins/extensions/pykrita/sip/krita/Node.sip
index a01a6bce6f..986cc54169 100644
--- a/plugins/extensions/pykrita/sip/krita/Node.sip
+++ b/plugins/extensions/pykrita/sip/krita/Node.sip
@@ -82,7 +82,7 @@ public Q_SLOTS:
int index() const;
QUuid uniqueId() const;
- void paintLine(const QPoint pointOne, const QPoint pointTwo, const QString strokeStyle = "ForegroundColor");
+ void paintLine(const QPoint pointOne, const QPoint pointTwo, double pressureOne = 1.0, double pressureTwo = 1.0, const QString strokeStyle = "ForegroundColor");
void paintRectangle(const QRectF &rect, const QString strokeStyle = "ForegroundColor", const QString fillStyle = "None");
void paintPolygon(const QList<QPointF> points, const QString strokeStyle = "ForegroundColor", const QString fillStyle = "None");
void paintEllipse(const QRectF &rect, const QString strokeStyle = "ForegroundColor", const QString fillStyle = "None");

View file

@ -0,0 +1,309 @@
diff --git a/libs/libkis/CMakeLists.txt b/libs/libkis/CMakeLists.txt
index a337451..66a5166 100644
--- a/libs/libkis/CMakeLists.txt
+++ b/libs/libkis/CMakeLists.txt
@@ -10,6 +10,7 @@ set(kritalibkis_LIB_SRCS
ManagedColor.cpp
Node.cpp
Notifier.cpp
+ PaintingResources.cpp
PresetChooser.cpp
Preset.cpp
Palette.cpp
diff --git a/libs/libkis/Document.h b/libs/libkis/Document.h
index 998a350..841c319 100644
--- a/libs/libkis/Document.h
+++ b/libs/libkis/Document.h
@@ -6,8 +6,6 @@
#ifndef LIBKIS_DOCUMENT_H
#define LIBKIS_DOCUMENT_H
-#include <QObject>
-
#include "kritalibkis_export.h"
#include "libkis.h"
diff --git a/libs/libkis/Node.cpp b/libs/libkis/Node.cpp
index 3fd7b5a..84f24d3 100644
--- a/libs/libkis/Node.cpp
+++ b/libs/libkis/Node.cpp
@@ -67,6 +67,17 @@
#include "LibKisUtils.h"
#include <kis_layer_utils.h>
+#include <KoCanvasResourceProvider.h>
+#include "strokes/KisFreehandStrokeInfo.h"
+#include "kis_resources_snapshot.h"
+#include "kis_canvas_resource_provider.h"
+#include "strokes/freehand_stroke.h"
+#include "kis_painting_information_builder.h"
+#include "KisAsynchronousStrokeUpdateHelper.h"
+#include "kis_stroke_strategy.h"
+#include "PaintingResources.h"
+
+
struct Node::Private {
Private() {}
KisImageWSP image;
@@ -821,3 +832,50 @@ KisNodeSP Node::node() const
{
return d->node;
}
+
+void Node::paintLine(const QPointF pointOne, const QPointF pointTwo)
+{
+ KisPaintInformation pointOneInfo;
+ pointOneInfo.setPressure(1.0);
+ pointOneInfo.setPos(pointOne);
+
+ KisPaintInformation pointTwoInfo;
+ pointTwoInfo.setPressure(1.0);
+ pointTwoInfo.setPos(pointTwo);
+
+ KisFigurePaintingToolHelper helper = PaintingResources::createHelper(d->image);
+ helper.paintLine(pointOneInfo, pointTwoInfo);
+}
+
+
+void Node::paintRectangle(const QRectF &rect)
+{
+ // reference class where this stuff is being done. Maybe can use the "facade" like that does for setup?
+ // void KisFigurePaintingToolHelper::paintRect(const QRectF &rect)
+
+ KisFigurePaintingToolHelper helper = PaintingResources::createHelper(d->image);
+ helper.paintRect(rect);
+}
+
+
+void Node::paintPolygon(const QList<QPointF> listPoint)
+{
+ // strategy needs points in vPointF format
+ QVector<QPointF> points = points.fromList(listPoint);
+ KisFigurePaintingToolHelper helper = PaintingResources::createHelper(d->image);
+ helper.paintPolygon(points);
+}
+
+
+void Node::paintEllipse(const QRectF &rect)
+{
+ KisFigurePaintingToolHelper helper = PaintingResources::createHelper(d->image);
+ helper.paintEllipse(rect);
+}
+
+
+void Node::paintPath(const QPainterPath &path)
+{
+ KisFigurePaintingToolHelper helper = PaintingResources::createHelper(d->image);
+ helper.paintPainterPath(path);
+}
diff --git a/libs/libkis/Node.h b/libs/libkis/Node.h
index 1a40372..10daba4 100644
--- a/libs/libkis/Node.h
+++ b/libs/libkis/Node.h
@@ -570,6 +570,36 @@ public Q_SLOTS:
*/
QUuid uniqueId() const;
+ /**
+ * @brief paint a line on the canvas. Uses current brush preset
+ * @param pointOne starting point
+ * @param pointTwo end point
+ */
+ void paintLine(const QPointF pointOne, const QPointF pointTwo);
+
+ /**
+ * @brief paint a rectangle on the canvas. Uses current brush preset
+ * @param rect QRect with x, y, width, and height
+ */
+ void paintRectangle(const QRectF &rect);
+
+ /**
+ * @brief paint a polygon on the canvas. Uses current brush preset
+ * @param list of Qpoints
+ */
+ void paintPolygon(const QList<QPointF> points);
+
+ /**
+ * @brief paint an ellipse on the canvas. Uses current brush preset
+ * @param rect QRect with x, y, width, and height
+ */
+ void paintEllipse(const QRectF &rect);
+
+ /**
+ * @brief paint a custom path on the canvas. Uses current brush preset
+ * @param path QPainterPath to determine path
+ */
+ void paintPath(const QPainterPath &path);
private:
diff --git a/libs/libkis/PaintingResources.cpp b/libs/libkis/PaintingResources.cpp
new file mode 100644
index 0000000..62abb26
--- /dev/null
+++ b/libs/libkis/PaintingResources.cpp
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2020 Scott Petrovic <scottpetrovic@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+#include "PaintingResources.h"
+
+#include <kis_types.h>
+
+#include "KisView.h"
+#include "kis_types.h"
+
+#include "kis_canvas_resource_provider.h"
+#include "kis_paintop_preset.h"
+
+#include "KisViewManager.h"
+#include "KisGlobalResourcesInterface.h"
+
+#include <KoResourcePaths.h>
+
+#include "Document.h"
+
+#include <KisPart.h>
+#include <KisMainWindow.h>
+
+#include <kis_types.h>
+#include <kis_annotation.h>
+
+
+
+#include "kis_animation_importer.h"
+#include <kis_canvas2.h>
+#include <KoUpdater.h>
+#include <QMessageBox>
+
+
+#include "strokes/KisFreehandStrokeInfo.h"
+#include "kis_resources_snapshot.h"
+#include "kis_canvas_resource_provider.h"
+#include "strokes/freehand_stroke.h"
+#include "kis_painting_information_builder.h"
+#include "KisAsynchronousStrokeUpdateHelper.h"
+#include "kis_stroke_strategy.h"
+
+
+KisFigurePaintingToolHelper PaintingResources::createHelper(KisImageWSP image)
+{
+ // need to grab the resource provider
+ KisView *activeView = KisPart::instance()->currentMainwindow()->activeView();
+ KoCanvasResourceProvider *resourceManager = activeView->viewManager()->canvasResourceProvider()->resourceManager();
+
+ // grab the image and current layer
+ KisNodeSP node = activeView->currentNode();
+
+ const KUndo2MagicString name = kundo2_noi18n("python_stroke");
+ KisFigurePaintingToolHelper helper(
+ name,
+ image,
+ node, resourceManager,
+ KisToolShapeUtils::StrokeStyle::StrokeStyleForeground,
+ KisToolShapeUtils::FillStyle::FillStyleNone
+ );
+
+ return helper;
+}
diff --git a/libs/libkis/PaintingResources.h b/libs/libkis/PaintingResources.h
new file mode 100644
index 0000000..19bb0d4
--- /dev/null
+++ b/libs/libkis/PaintingResources.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2020 Scott Petrovic <scottpetrovic@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+#ifndef LIBKIS_PAINTINGRESOURCES_H
+#define LIBKIS_PAINTINGRESOURCES_H
+
+#include <QObject>
+#include <QColor>
+#include <kis_types.h>
+
+#include "kritalibkis_export.h"
+#include "KoCanvasResourceProvider.h"
+#include "kis_stroke_strategy.h"
+
+#include <kis_figure_painting_tool_helper.h>
+
+#include "libkis.h"
+
+#include "View.h"
+
+/**
+ * @brief The PaintingResources namespace
+ * Sets up information related to making painting strokes.
+ * Used primarily in the Document class
+ *
+ */
+namespace PaintingResources
+{
+ KisFigurePaintingToolHelper createHelper(KisImageWSP image);
+};
+
+#endif // LIBKIS_PAINTINGRESOURCES_H
diff --git a/libs/ui/tool/kis_resources_snapshot.cpp b/libs/ui/tool/kis_resources_snapshot.cpp
index dca6ba5..0c031e0 100644
--- a/libs/ui/tool/kis_resources_snapshot.cpp
+++ b/libs/ui/tool/kis_resources_snapshot.cpp
@@ -75,8 +75,11 @@ KisResourcesSnapshot::KisResourcesSnapshot(KisImageSP image, KisNodeSP currentNo
m_d->currentBgColor = resourceManager->resource(KoCanvasResource::BackgroundColor).value<KoColor>();
m_d->currentPattern = resourceManager->resource(KoCanvasResource::CurrentPattern).value<KoPatternSP>();
if (resourceManager->resource(KoCanvasResource::CurrentGradient).value<KoAbstractGradientSP>()) {
- m_d->currentGradient = resourceManager->resource(KoCanvasResource::CurrentGradient).value<KoAbstractGradientSP>()
- ->cloneAndBakeVariableColors(m_d->globalCanvasResourcesInterface);
+ m_d->currentGradient = resourceManager->resource(KoCanvasResource::CurrentGradient).value<KoAbstractGradientSP>();
+ if(m_d->currentGradient) {
+ m_d->currentGradient = resourceManager->resource(KoCanvasResource::CurrentGradient).value<KoAbstractGradientSP>()
+ ->cloneAndBakeVariableColors(m_d->globalCanvasResourcesInterface);
+ }
}
/**
diff --git a/plugins/extensions/pykrita/sip/krita/Node.sip b/plugins/extensions/pykrita/sip/krita/Node.sip
index 3066bfb..cbcef0f 100644
--- a/plugins/extensions/pykrita/sip/krita/Node.sip
+++ b/plugins/extensions/pykrita/sip/krita/Node.sip
@@ -69,6 +69,12 @@ public Q_SLOTS:
void setLayerStyleFromAsl(const QString &asl);
int index() const;
QUuid uniqueId() const;
+
+ void paintLine(const QPoint pointOne, const QPoint pointTwo);
+ void paintRectangle(const QRectF &rect);
+ void paintPolygon(const QList<QPointF> points);
+ void paintEllipse(const QRectF &rect);
+ void paintPath(const QPainterPath &path);
Q_SIGNALS:
private:
};

View file

@ -0,0 +1,26 @@
From 7fead3cd2158fe913775ede5651291cf5f4ccf4d Mon Sep 17 00:00:00 2001
From: chayleaf <chayleaf-git@pavluk.org>
Date: Wed, 14 Aug 2024 07:32:11 +0700
Subject: [PATCH 1/2] mobile: reverse layer order
This makes exclusive anchored layers that were added first be first
---
sway/desktop/layer_shell.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/sway/desktop/layer_shell.c b/sway/desktop/layer_shell.c
index 8c6cedfe..41a638ee 100644
--- a/sway/desktop/layer_shell.c
+++ b/sway/desktop/layer_shell.c
@@ -129,7 +129,7 @@ static void arrange_layer(struct sway_output *output, struct wl_list *list,
struct wlr_box full_area = { 0 };
wlr_output_effective_resolution(output->wlr_output,
&full_area.width, &full_area.height);
- wl_list_for_each(sway_layer, list, link) {
+ wl_list_for_each_reverse(sway_layer, list, link) {
struct wlr_layer_surface_v1 *layer = sway_layer->layer_surface;
struct wlr_layer_surface_v1_state *state = &layer->current;
if (exclusive != (state->exclusive_zone > 0)) {
--
2.44.1

View file

@ -0,0 +1,27 @@
From 466d750b96184b793d276d9d0e30d37af4477fea Mon Sep 17 00:00:00 2001
From: chayleaf <chayleaf-git@pavluk.org>
Date: Wed, 14 Aug 2024 08:17:44 +0700
Subject: [PATCH 2/2] mobile: don't idle_notify for volume keys
---
sway/input/keyboard.c | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/sway/input/keyboard.c b/sway/input/keyboard.c
index 8927287f..d42f62b0 100644
--- a/sway/input/keyboard.c
+++ b/sway/input/keyboard.c
@@ -404,7 +404,9 @@ static void handle_key_event(struct sway_keyboard *keyboard,
keyboard->seat_device->input_device->wlr_device;
char *device_identifier = input_device_get_identifier(wlr_device);
bool exact_identifier = keyboard->wlr->group != NULL;
- seat_idle_notify_activity(seat, IDLE_SOURCE_KEYBOARD);
+ if (event->keycode != XKB_KEY_XF86AudioLowerVolume && event->keycode != XKB_KEY_XF86AudioRaiseVolume) {
+ seat_idle_notify_activity(seat, IDLE_SOURCE_KEYBOARD);
+ }
bool input_inhibited = seat->exclusive_client != NULL ||
server.session_lock.locked;
struct sway_keyboard_shortcuts_inhibitor *sway_inhibitor =
--
2.44.1

View file

@ -1,4 +1,5 @@
{ config
, lib
, pkgs
, router-config
, hardware
@ -126,14 +127,16 @@ in
options = [ "defaults" "size=2G" "mode=755" ]; };
"/persist" =
{ device = "UUID=${uuids.bch}"; fsType = "bcachefs"; inherit neededForBoot;
# TODO: https://github.com/systemd/systemd/pull/33720/files
options = [
# TODO: remove the if when systemd >= 257
options = let
dep = if lib.versionAtLeast config.boot.initrd.systemd.package.version "257" then "wants" else "requires";
in [
"degraded"
"errors=ro"
"x-systemd.device-timeout=0"
"x-systemd.requires=dev-mapper-bch0.device"
"x-systemd.requires=dev-mapper-bch1.device"
"x-systemd.requires=dev-mapper-bch2.device"
"x-systemd.${dep}=dev-mapper-bch0.device"
"x-systemd.${dep}=dev-mapper-bch1.device"
"x-systemd.${dep}=dev-mapper-bch2.device"
]; };
"/boot" = { device = parts.boot; fsType = "vfat"; inherit neededForBoot; };
};

View file

@ -53,6 +53,7 @@
"steam-original"
];
hardware = {
opentabletdriver.enable = true;
steam-hardware.enable = true;
opengl.driSupport32Bit = true;
# needed for sway WLR_RENDERER=vulkan

20
system/hosts/router/a.py Normal file
View file

@ -0,0 +1,20 @@
import traceback
import pytricia # type: ignore
def build_ipset(ips: list[str]) -> pytricia.PyTricia:
pyt = pytricia.PyTricia()
for ip in ips:
try:
pyt.insert(ip, None)
except:
with open("/var/lib/unbound/error.log", "at") as f:
f.write(f"Warning: couldn't insert ip {ip}:\n")
traceback.print_exc(file=f)
return pyt
s = build_ipset(["10.0.0.0/24"])

View file

@ -178,12 +178,8 @@
#
import gi
import ipaddress
import json
import os
import subprocess
import pydbus
import pytricia # type: ignore
import re
import array
import threading
@ -205,22 +201,6 @@ from typing import TypedDict, Optional, Any
IF_UNSPEC = -1
PROTO_UNSPEC = -1
Domains = dict[str, "Domains | bool"]
class NftQuery(TypedDict):
domains: Domains
ips4: pytricia.PyTricia
ips6: pytricia.PyTricia
name4: str
name6: str
dynamic: bool
NFT_QUERIES: dict[str, NftQuery] = {}
# dynamic query update token
NFT_TOKEN: str = ""
DOMAIN_NAME_OVERRIDES: dict[str, str] = {}
DEBUG = False
MDNS_TTL: int
@ -239,22 +219,6 @@ dbus_thread: threading.Thread
dbus_loop: Any
def is_valid_ip4(x: str) -> bool:
try:
_ = ipaddress.IPv4Address(x)
return True
except ipaddress.AddressValueError:
return False
def is_valid_ip6(x: str) -> bool:
try:
_ = ipaddress.IPv6Address(x)
return True
except ipaddress.AddressValueError:
return False
def str2bool(v: str) -> bool:
if v.lower() in ["false", "no", "0", "off", ""]:
return False
@ -500,150 +464,11 @@ def parse_type_list(lst: str) -> list[RdataType]:
)
def build_ipset(ips: list[str]) -> pytricia.PyTricia:
pyt = pytricia.PyTricia()
for ip in ips:
try:
pyt.insert(ip, None)
except:
with open("/var/lib/unbound/error.log", "at") as f:
f.write(f"Warning: couldn't insert ip {ip}:\n")
traceback.print_exc(file=f)
return pyt
def add_ips(set: str, ipv6: bool, ips: list[str], flush: bool = False):
# with open('/var/lib/unbound/info.log', 'at') as f:
# print('set', set, 'ipv6', ipv6, 'ips', ips, file=f)
pyt = build_ipset(ips)
ruleset: list[dict] = []
if flush:
ruleset.append(
{"flush": {"set": {"family": "inet", "table": "global", "name": set}}}
)
elems: list[str | dict] = []
if ipv6:
maxn = 128
is_valid: Callable[[str], bool] = is_valid_ip6
else:
maxn = 32
is_valid = is_valid_ip4
for ip in pyt.keys():
try:
if pyt.parent(ip) != None:
continue
except:
pass
if "/" not in ip:
n: int = maxn
else:
ip, n0 = ip.split("/")
try:
n = int(n0)
except:
continue
if not is_valid(ip):
continue
if n == maxn:
elems.append(ip)
else:
elems.append({"prefix": {"addr": ip, "len": n}})
# with open('/var/lib/unbound/info.log', 'at') as f:
# print('elems', elems, file=f)
if len(elems) == 0:
return
ruleset.append(
{
"add": {
"element": {
"family": "inet",
"table": "global",
"name": set,
"elem": elems,
}
}
}
)
data: bytes = json.dumps({"nftables": ruleset}).encode("utf-8")
# with open('/var/lib/unbound/info.log', 'at') as f:
# print('data', data, file=f)
try:
out = subprocess.run(
["/run/current-system/sw/bin/nft", "-j", "-f", "/dev/stdin"],
capture_output=True,
input=data,
)
# with open('/var/lib/unbound/info.log', 'at') as f:
# print('out', out, file=f)
if out.returncode != 0:
with open("/var/lib/unbound/nftables.log", "wb") as f:
f.write(b"Error running nftables ruleset. Ruleset:\n")
f.write(data)
f.write(b"\n")
f.write(b"stdout:\n")
f.write(out.stdout)
f.write(b"\nstderr:\n")
f.write(out.stderr)
f.write(b"\n")
except:
with open("/var/lib/unbound/error.log", "at") as f:
f.write(f"While adding ips for set {set}:\n")
traceback.print_exc(file=f)
def add_split_domain(domains: Domains, split_domain: list[str]):
if not split_domain:
return
split_domain = split_domain[:]
if split_domain and split_domain[-1] == '*':
split_domain.pop()
if not split_domain:
return
while len(split_domain) > 1:
key = split_domain[-1]
if key in domains.keys():
domains1 = domains[key]
if isinstance(domains1, bool):
return
else:
domains1 = {}
domains[key] = domains1
domains = domains1
split_domain.pop()
domains[split_domain[-1]] = True
def build_domains(domains: list[str]) -> Domains:
ret: Domains = {}
for domain in domains:
add_split_domain(ret, domain.split("."))
return ret
def lookup_domain(domains: Domains, domain: str) -> bool:
split_domain: list[str] = domain.split(".")
while len(split_domain):
key: str = split_domain[-1]
split_domain = split_domain[:-1]
domains1 = domains.get(key, False)
if isinstance(domains1, bool):
return domains1
domains = domains1
return False
class DpiInfo(TypedDict):
domains: list[str]
name: str
restriction: dict
def init(*args: Any, **kwargs: Any):
global dbus_thread, DEBUG
global MDNS_TTL, MDNS_GETONE, MDNS_TIMEOUT
global MDNS_REJECT_TYPES, MDNS_ACCEPT_TYPES
global MDNS_REJECT_NAMES, MDNS_ACCEPT_NAMES
global NFT_QUERIES, NFT_TOKEN, DOMAIN_NAME_OVERRIDES
domain_name_overrides: str = os.environ.get("DOMAIN_NAME_OVERRIDES", "")
if domain_name_overrides:
@ -652,92 +477,6 @@ def init(*args: Any, **kwargs: Any):
DOMAIN_NAME_OVERRIDES[k1] = v1
DOMAIN_NAME_OVERRIDES[k1 + "."] = v1 + "."
NFT_TOKEN = os.environ.get("NFT_TOKEN", "")
nft_queries: str = os.environ.get("NFT_QUERIES", "")
if nft_queries:
for query in nft_queries.split(";"):
name, sets = query.split(":")
dynamic = False
if name.endswith("!"):
name = name.rstrip("!")
dynamic = True
set4, set6 = sets.split(",")
NFT_QUERIES[name] = {
"domains": {},
"ips4": [],
"ips6": [],
"name4": set4,
"name6": set6,
"dynamic": dynamic,
}
for k, v in NFT_QUERIES.items():
all_domains: list[str] = []
for base in ["/etc/unbound", "/var/lib/unbound"]:
try:
with open(f"{base}/{k}_domains.json", "rt", encoding="utf-8") as f:
domains: list[str] = json.load(f)
all_domains.extend(domains)
except FileNotFoundError:
pass
except:
with open("/var/lib/unbound/error.log", "at") as f:
traceback.print_exc(file=f)
try:
with open(f"{base}/{k}_dpi.json", "rt", encoding="utf-8") as f:
dpi: list[DpiInfo] = json.load(f)
for dpi_info in dpi:
all_domains.extend(dpi_info["domains"])
except FileNotFoundError:
pass
except:
with open("/var/lib/unbound/error.log", "at") as f:
traceback.print_exc(file=f)
try:
with open(f"{base}/{k}_ips.json", "rt", encoding="utf-8") as f:
ips: list[str] = json.load(f)
v["ips4"].extend(filter(lambda x: "." in x, ips))
v["ips6"].extend(filter(lambda x: ":" in x, ips))
except FileNotFoundError:
pass
except:
with open("/var/lib/unbound/error.log", "at") as f:
traceback.print_exc(file=f)
v["domains"] = build_domains(all_domains)
# cached resolved domains
try:
os.makedirs("/var/lib/unbound/domains4/", exist_ok=True)
for x in os.listdir("/var/lib/unbound/domains4/"):
with open(f"/var/lib/unbound/domains4/{x}", "rt") as f:
data = f.read().split("\n")
for k, v in NFT_QUERIES.items():
if lookup_domain(v["domains"], x):
v["ips4"].extend(data)
except:
with open("/var/lib/unbound/error.log", "at") as f:
traceback.print_exc(file=f)
try:
os.makedirs("/var/lib/unbound/domains6/", exist_ok=True)
for x in os.listdir("/var/lib/unbound/domains6/"):
with open(f"/var/lib/unbound/domains6/{x}", "rt") as f:
data = f.read().split("\n")
for k, v in NFT_QUERIES.items():
if lookup_domain(v["domains"], x):
v["ips6"].extend(data)
except:
with open("/var/lib/unbound/error.log", "at") as f:
traceback.print_exc(file=f)
# finally, add the ips to nftables
for k, v in NFT_QUERIES.items():
if v["ips4"] and v["name4"]:
add_ips(v["name4"], False, v["ips4"], flush=True)
if v["ips6"] and v["name6"]:
add_ips(v["name6"], True, v["ips6"], flush=True)
v["ips4"] = build_ipset(v["ips4"])
v["ips6"] = build_ipset(v["ips6"])
DEBUG = str2bool(os.environ.get("DEBUG", str(False)))
MDNS_TTL = int(os.environ.get("MDNS_TTL", 120))
@ -751,20 +490,20 @@ def init(*args: Any, **kwargs: Any):
if MDNS_ACCEPT_TYPES:
dbg(f"ONLY resolving the following types via Avahi: {MDNS_ACCEPT_TYPES}")
v2 = os.environ.get("MDNS_REJECT_NAMES", None)
MDNS_REJECT_NAMES = re.compile(v2, flags=re.I | re.S) if v2 is not None else None
v = os.environ.get("MDNS_REJECT_NAMES", None)
MDNS_REJECT_NAMES = re.compile(v, flags=re.I | re.S) if v is not None else None
if MDNS_REJECT_NAMES is not None:
dbg(f"Names NOT resolved via Avahi: {MDNS_REJECT_NAMES.pattern}")
v2 = os.environ.get("MDNS_ACCEPT_NAMES", None)
MDNS_ACCEPT_NAMES = re.compile(v2, flags=re.I | re.S) if v2 is not None else None
v = os.environ.get("MDNS_ACCEPT_NAMES", None)
MDNS_ACCEPT_NAMES = re.compile(v, flags=re.I | re.S) if v is not None else None
if MDNS_ACCEPT_NAMES is not None:
dbg(
f"ONLY resolving the following names via Avahi: {MDNS_ACCEPT_NAMES.pattern}"
)
v2 = os.environ.get("MDNS_TIMEOUT", None)
MDNS_TIMEOUT = int(v2) if v2 is not None else None
v = os.environ.get("MDNS_TIMEOUT", None)
MDNS_TIMEOUT = int(v) if v is not None else None
if MDNS_TIMEOUT is not None:
dbg(f"Avahi request timeout: {MDNS_TIMEOUT}")
@ -824,139 +563,6 @@ def operate(id, event, qstate, qdata) -> bool:
n2: str = name.rstrip(".")
if NFT_TOKEN and n2.endswith(f"{NFT_TOKEN}"):
if n2.endswith(f".{NFT_TOKEN}"):
n3 = n2.removesuffix(f".{NFT_TOKEN}")
for k, v in NFT_QUERIES.items():
if v["dynamic"] and n3.endswith(f".{k}"):
n4 = n3.removesuffix(f".{k}")
qdomains = v["domains"]
if not lookup_domain(qdomains, n4):
add_split_domain(qdomains, n4.split("."))
old = []
if os.path.exists(f"/var/lib/unbound/{k}_domains.json"):
with open(f"/var/lib/unbound/{k}_domains.json", "rt") as f:
old = json.load(f)
os.rename(
f"/var/lib/unbound/{k}_domains.json",
f"/var/lib/unbound/{k}_domains.json.bak",
)
old.append(n4)
with open(f"/var/lib/unbound/{k}_domains.json", "wt") as f:
json.dump(old, f)
elif n2.endswith(f".tmp{NFT_TOKEN}"):
n3 = n2.removesuffix(f".tmp{NFT_TOKEN}")
for k, v in NFT_QUERIES.items():
if v["dynamic"] and n3.endswith(f".{k}"):
n4 = n3.removesuffix(f".{k}")
qdomains = v["domains"]
if not lookup_domain(qdomains, n4):
add_split_domain(qdomains, n4.split("."))
return True
qnames: list[str] = []
for k, v in NFT_QUERIES.items():
if lookup_domain(v["domains"], n2):
qnames.append(k)
# THIS IS PAIN
if qnames:
try:
ip4: list[str] = []
ip6: list[str] = []
if qstate.return_msg and qstate.return_msg.rep:
rep = qstate.return_msg.rep
for i in range(rep.rrset_count):
d = rep.rrsets[i].entry.data
rk = rep.rrsets[i].rk
for j in range(0, d.count + d.rrsig_count):
wire = array.array("B", d.rr_data[j]).tobytes()
# IN
if rk.rrset_class != 256:
continue
# A, AAAA
if (
rk.type == 256
and len(wire) == 4 + 2
and wire[:2] == b"\x00\x04"
):
ip4.append(".".join(str(x) for x in wire[2:]))
elif (
rk.type == 7168
and len(wire) == 16 + 2
and wire[:2] == b"\x00\x10"
):
b = list(hex(x)[2:].zfill(2) for x in wire[2:])
ip6.append(
":".join(
"".join(b[x : x + 2]) for x in range(0, len(b), 2)
)
)
changed4 = False
changed6 = False
if ip4:
new_data = "\n".join(sorted(ip4))
try:
with open("/var/lib/unbound/domains4/" + n2, "rt") as f:
old_data = f.read()
except:
old_data = ""
if old_data != new_data:
changed4 = True
with open("/var/lib/unbound/domains4/" + n2, "wt") as f:
f.write(new_data)
if ip6:
new_data = "\n".join(sorted(ip6))
try:
with open("/var/lib/unbound/domains6/" + n2, "rt") as f:
old_data = f.read()
except:
old_data = ""
if old_data != new_data:
changed6 = True
with open("/var/lib/unbound/domains6/" + n2, "wt") as f:
f.write(new_data)
if changed4:
for qname in qnames:
q = NFT_QUERIES[qname]
name4 = q["name4"]
ips4 = q["ips4"]
if name4:
ip2 = []
for ip in ip4:
exists = False
try:
if ips4.has_key(ip) or ips4.parent(ip) != None:
exists = True
except:
pass
if not exists:
ips4.insert(ip, None)
ip2.append(ip)
if ip2:
add_ips(name4, False, ip2)
if changed6:
for qname in qnames:
q = NFT_QUERIES[qname]
name6 = q["name6"]
ips6 = q["ips6"]
if name6:
ip2 = []
for ip in ip6:
exists = False
try:
if ips6.has_key(ip) or ips6.parent(ip) != None:
exists = True
except:
pass
if not exists:
ips6.insert(ip, None)
ip2.append(ip)
if ip2:
add_ips(name6, True, ip2)
except:
with open("/var/lib/unbound/error.log", "at") as f:
traceback.print_exc(file=f)
if event == MODULE_EVENT_NEW or event == MODULE_EVENT_PASS:
qstate.ext_state[id] = MODULE_WAIT_MODULE
return True
@ -1031,13 +637,11 @@ def operate(id, event, qstate, qdata) -> bool:
if not m.set_return_msg(qstate):
raise Exception("Error in set_return_msg")
# For some reason this breaks everything! Unbound responds with SERVFAIL instead of using the cache
# i.e. the first response is fine, but loading it from cache just doesn't work
# Resolution via Avahi works fast anyway so whatever
# if not storeQueryInCache(qstate, qstate.return_msg.qinfo, qstate.return_msg.rep, 0):
# raise Exception("Error in storeQueryInCache")
qstate.return_msg.rep.security = 2
if not storeQueryInCache(qstate, qstate.return_msg.qinfo, qstate.return_msg.rep, 0):
raise Exception("Error in storeQueryInCache")
qstate.return_rcode = Rcode.NOERROR
return True

View file

@ -4,7 +4,8 @@
, lib
, router-lib
, server-config
, ... }:
, ...
}:
let
cfg = config.router-settings;
@ -473,8 +474,8 @@ in {
gateways = [ netAddresses.lan6 ];
radvdSettings.AdvAutonomous = true;
coreradSettings.autonomous = true;
# don't allocate addresses for most devices
keaSettings.pools = [ ];
# # don't allocate addresses for most devices
# keaSettings.pools = [ ];
# just assign the reservations
keaSettings.reservations = map (res:
(if res.duid != null then { duid = res.duid; } else { hw-address = res.macAddress; }) // {
@ -597,20 +598,21 @@ in {
[(is.eq ip.saddr "@block4") (log "block4/s ") drop]
[(is.eq ip6.saddr "@block6") (log "block6/s ") drop]
# default to no vpn...
[(mangle meta.mark wan_table)]
# # default to vpn...
# [(mangle meta.mark vpn_table)]
# [(mangle meta.mark wan_table)]
# default to vpn...
[(mangle meta.mark vpn_table)]
[(is.eq meta.mark 0)]
# ...but unvpn traffic to/from force_unvpn4/force_unvpn6
[(is.eq ip.daddr "@force_unvpn4") (mangle meta.mark wan_table)]
[(is.eq ip6.daddr "@force_unvpn6") (mangle meta.mark wan_table)]
[(is.eq ip.saddr "@force_unvpn4") (mangle meta.mark wan_table)]
[(is.eq ip6.saddr "@force_unvpn6") (mangle meta.mark wan_table)]
# ...force vpn to/from force_vpn4/force_vpn6
# (disable this if it breaks some sites)
[(is.eq ip.daddr "@force_vpn4") (mangle meta.mark vpn_table)]
[(is.eq ip6.daddr "@force_vpn6") (mangle meta.mark vpn_table)]
[(is.eq ip.saddr "@force_vpn4") (mangle meta.mark vpn_table)]
[(is.eq ip6.saddr "@force_vpn6") (mangle meta.mark vpn_table)]
# ...but unvpn traffic to/from force_unvpn4/force_unvpn6
[(is.eq ip.daddr "@force_unvpn4") (mangle meta.mark wan_table)]
[(is.eq ip6.daddr "@force_unvpn6") (mangle meta.mark wan_table)]
[(is.eq ip.saddr "@force_unvpn4") (mangle meta.mark wan_table)]
[(is.eq ip6.saddr "@force_unvpn6") (mangle meta.mark wan_table)]
# block requests to port 25 from hosts other than the server so they can't send mail pretending to originate from my domain
# only do this for lans since traffic from other interfaces isn't forwarded to wan
[(is.eq meta.iifname lanSet) (is.ne ether.saddr cfg.serverMac) (is.eq meta.l4proto (f: f.tcp)) (is.eq tcp.dport 25) (log "smtp ") drop]
@ -856,7 +858,7 @@ in {
access-control = [ "${netCidrs.netns4} allow" "${netCidrs.netns6} allow" "${netCidrs.lan4} allow" "${netCidrs.lan6} allow" ];
aggressive-nsec = true;
do-ip6 = true;
module-config = ''"validator python iterator"'';
module-config = ''"validator dynlib python iterator"'';
local-zone = [
# incompatible with avahi resolver
# ''"local." static''
@ -888,6 +890,7 @@ in {
# normally it would refer to the flake path, but then the service changes on every flake update
# instead, write a new file in nix store
python.python-script = builtins.toFile "avahi-resolver-v2.py" (builtins.readFile ./avahi-resolver-v2.py);
dynlib.dynlib-file = "${pkgs.unbound-mod}/lib/libunbound_mod.so";
remote-control.control-enable = true;
};
};
@ -907,7 +910,7 @@ in {
networking.hosts."${serverAddress6}" = hosted-domains;
systemd.services.unbound = lib.mkIf config.services.unbound.enable {
environment.PYTHONPATH = let
unbound-python = pkgs.python3.withPackages (ps: with ps; [ pydbus dnspython requests pytricia nftables ]);
unbound-python = pkgs.python3.withPackages (ps: with ps; [ pydbus dnspython ]);
in
"${unbound-python}/${unbound-python.sitePackages}";
# see https://github.com/NixOS/nixpkgs/pull/310514

View file

@ -78,6 +78,8 @@ in {
{ directory = /var/lib/certspotter; user = "certspotter"; group = "certspotter"; mode = "0755"; }
] ++ lib.optionals (config.services.coop-fd.enable or false) [
{ directory = /var/lib/private/coop-fd; mode = "0750"; defaultPerms.mode = "0700"; }
] ++ lib.optionals config.virtualisation.docker.enable [
{ directory = /var/lib/docker; user = "root"; group = "root"; mode = "0710"; }
] ++ lib.optionals config.services.dovecot2.enable [
{ directory = /var/lib/dhparams; user = "root"; group = "root"; mode = "0755"; }
{ directory = /var/lib/dovecot; user = "root"; group = "root"; mode = "0755"; }