about summary refs log tree commit diff
path: root/modules/workstation/windowing
diff options
context:
space:
mode:
Diffstat (limited to 'modules/workstation/windowing')
-rw-r--r--modules/workstation/windowing/alacritty/config.yml114
-rw-r--r--modules/workstation/windowing/alacritty/default.nix11
-rw-r--r--modules/workstation/windowing/default.nix13
-rw-r--r--modules/workstation/windowing/firefox/default.nix19
-rw-r--r--modules/workstation/windowing/firefox/profiles.ini8
-rw-r--r--modules/workstation/windowing/firefox/user.js1
-rw-r--r--modules/workstation/windowing/gtk/default.nix9
-rw-r--r--modules/workstation/windowing/gtk/settings.ini2
-rw-r--r--modules/workstation/windowing/sway/choose_workspace.nix9
-rw-r--r--modules/workstation/windowing/sway/choose_workspace.sh.in17
-rw-r--r--modules/workstation/windowing/sway/config.in125
-rw-r--r--modules/workstation/windowing/sway/default.nix47
-rw-r--r--modules/workstation/windowing/sway/status.cpp203
-rw-r--r--modules/workstation/windowing/sway/status.nix6
-rw-r--r--modules/workstation/windowing/sway/swayidle/default.nix23
-rw-r--r--modules/workstation/windowing/sway/swaylock/config.in3
-rw-r--r--modules/workstation/windowing/sway/swaylock/default.nix12
-rw-r--r--modules/workstation/windowing/sway/wallpaper.nix10
-rw-r--r--modules/workstation/windowing/sway/wlsunset/default.nix9
-rw-r--r--modules/workstation/windowing/sway/xdg-desktop-portal-wlr/default.nix13
20 files changed, 654 insertions, 0 deletions
diff --git a/modules/workstation/windowing/alacritty/config.yml b/modules/workstation/windowing/alacritty/config.yml
new file mode 100644
index 000000000000..856a7aa5af29
--- /dev/null
+++ b/modules/workstation/windowing/alacritty/config.yml
@@ -0,0 +1,114 @@
+window:
+  # If both are `0`, this setting is ignored.
+  dimensions:
+    columns: 80
+    lines: 24
+
+  padding:
+    x: 2
+    y: 2
+
+  dynamic_padding: true
+  decorations: none
+  opacity: 0.8
+  startup_mode: Windowed
+
+scrolling:
+  history: 0 # disabled
+
+font:
+  normal:
+    family: monospace
+    #style: Regular
+
+  bold:
+    family: monospace
+    #style: Bold
+
+  italic:
+    family: monospace
+    #style: Italic
+
+  offset:
+    x: 0
+    y: 0
+
+  size: 11.5
+
+  glyph_offset:
+    x: 0
+    y: 0
+
+draw_bold_text_with_bright_colors: false
+
+colors:
+  primary:
+    background: '0x000000'
+    foreground: '0xffffff'
+    # dim_foreground: auto
+    # bright_foreground: normal foreground color
+
+  cursor:
+    text:   '0x161616'
+    cursor: '0xfcdc07'
+
+  normal:
+    black:   '0x2e3436'
+    red:     '0xb40000'
+    green:   '0x307000'
+    yellow:  '0xce5c00'
+    blue:    '0x3465a4'
+    magenta: '0x75507b'
+    cyan:    '0x06989a'
+    white:   '0xd3d7cf'
+
+  bright:
+    black:   '0x555753'
+    red:     '0xcc0000'
+    green:   '0x4e9a06'
+    yellow:  '0xedd400'
+    blue:    '0x729fcf'
+    magenta: '0xad7fa8'
+    cyan:    '0x34e2e2'
+    white:   '0xeeeeec'
+
+  # dim: default
+  # indexed_colors: default
+
+hints:
+  enabled: []
+
+# Available fields:
+#   - mouse
+#   - action
+#   - mods (optional)
+#
+# Values for `mouse`:
+#   - Middle
+#   - Left
+#   - Right
+#   - Numeric identifier such as `5`
+#
+# All available `mods` and `action` values are documented in the key binding
+# section.
+mouse_bindings:
+  - { mouse: Middle, action: PasteSelection }
+
+mouse:
+  double_click: { threshold: 300 }
+  triple_click: { threshold: 300 }
+  hide_when_typing: false
+
+selection:
+  semantic_escape_chars: ",│`|:\"' ()[]{}<>"
+
+  # When set to `true`, selected text will be copied to both the primary and
+  # the selection clipboard. Otherwise, it will only be copied to the selection
+  # clipboard.
+  save_to_clipboard: false
+
+cursor:
+  style: Block
+  unfocused_hollow: true
+
+live_config_reload: true
diff --git a/modules/workstation/windowing/alacritty/default.nix b/modules/workstation/windowing/alacritty/default.nix
new file mode 100644
index 000000000000..e8d086f54d8c
--- /dev/null
+++ b/modules/workstation/windowing/alacritty/default.nix
@@ -0,0 +1,11 @@
+{ pkgs, ... }:
+
+{
+  imports = [ ../../../xdg ];
+
+  environment.systemPackages = with pkgs;
+    lib.optional (!stdenv.isDarwin) alacritty;
+
+  users.users.qyliss.xdg.config.paths."alacritty/alacritty.yml" =
+    pkgs.copyPathToStore ./config.yml;
+}
diff --git a/modules/workstation/windowing/default.nix b/modules/workstation/windowing/default.nix
new file mode 100644
index 000000000000..af8ca84fdd42
--- /dev/null
+++ b/modules/workstation/windowing/default.nix
@@ -0,0 +1,13 @@
+{ pkgs, ... }:
+
+{
+  imports = [
+    ./alacritty ./firefox ./gtk ./sway
+  ];
+
+  environment.systemPackages = with pkgs; [
+    breeze-icons gnome3.adwaita-icon-theme gnome3.gnome-mines
+    gnome-podcasts hicolor-icon-theme imv pinball playerctl
+    wf-recorder
+  ];
+}
diff --git a/modules/workstation/windowing/firefox/default.nix b/modules/workstation/windowing/firefox/default.nix
new file mode 100644
index 000000000000..42f29c778965
--- /dev/null
+++ b/modules/workstation/windowing/firefox/default.nix
@@ -0,0 +1,19 @@
+{ config, pkgs, ... }:
+
+let
+  stateDir = "${config.users.users.qyliss.home}/state/mozilla";
+in
+
+{
+  systemd.tmpfiles.rules = [
+    "d ${stateDir}                 0700 qyliss qyliss"
+    "d ${stateDir}/firefox         0700 qyliss qyliss"
+    "d ${stateDir}/firefox/default 0700 qyliss qyliss"
+    "L+ ${stateDir}/firefox/profiles.ini - - - - ${./profiles.ini}"
+    "L+ ${stateDir}/firefox/user.js      - - - - ${./user.js}"
+  ];
+
+  environment.systemPackages = with pkgs; [ firefox-wayland ];
+
+  environment.variables.BROWSER = "firefox";
+}
diff --git a/modules/workstation/windowing/firefox/profiles.ini b/modules/workstation/windowing/firefox/profiles.ini
new file mode 100644
index 000000000000..becf53354e76
--- /dev/null
+++ b/modules/workstation/windowing/firefox/profiles.ini
@@ -0,0 +1,8 @@
+[General]
+StartWithLastProfile=1
+
+[Profile0]
+Name=default
+IsRelative=1
+Path=default
+Default=1
diff --git a/modules/workstation/windowing/firefox/user.js b/modules/workstation/windowing/firefox/user.js
new file mode 100644
index 000000000000..8b137891791f
--- /dev/null
+++ b/modules/workstation/windowing/firefox/user.js
@@ -0,0 +1 @@
+
diff --git a/modules/workstation/windowing/gtk/default.nix b/modules/workstation/windowing/gtk/default.nix
new file mode 100644
index 000000000000..df66f8fbb72d
--- /dev/null
+++ b/modules/workstation/windowing/gtk/default.nix
@@ -0,0 +1,9 @@
+{ pkgs, ... }:
+
+{
+  users.users.qyliss.xdg.config.paths."gtk-3.0/settings.ini" =
+    pkgs.copyPathToStore ./settings.ini;
+
+  # Needed for Dino to not draw its title bar.
+  environment.variables.GTK_CSD = "0";
+}
diff --git a/modules/workstation/windowing/gtk/settings.ini b/modules/workstation/windowing/gtk/settings.ini
new file mode 100644
index 000000000000..29322c1b3a0d
--- /dev/null
+++ b/modules/workstation/windowing/gtk/settings.ini
@@ -0,0 +1,2 @@
+[Settings]
+gtk-application-prefer-dark-theme=1
diff --git a/modules/workstation/windowing/sway/choose_workspace.nix b/modules/workstation/windowing/sway/choose_workspace.nix
new file mode 100644
index 000000000000..fc162d627b60
--- /dev/null
+++ b/modules/workstation/windowing/sway/choose_workspace.nix
@@ -0,0 +1,9 @@
+{ substituteAll, bemenu, jq }:
+
+substituteAll {
+  dir = "bin";
+  name = "choose_workspace";
+  src = ./choose_workspace.sh.in;
+  isExecutable = true;
+  inherit bemenu jq;
+}
diff --git a/modules/workstation/windowing/sway/choose_workspace.sh.in b/modules/workstation/windowing/sway/choose_workspace.sh.in
new file mode 100644
index 000000000000..963746e0c810
--- /dev/null
+++ b/modules/workstation/windowing/sway/choose_workspace.sh.in
@@ -0,0 +1,17 @@
+#! @shell@ -ue
+swaymsg -t get_workspaces |
+    @jq@/bin/jq -r \
+        '(to_entries | map(select(.value.focused)) | .[0].key), .[].name' |
+    (
+        read index
+        exec @bemenu@/bin/bemenu \
+            -p workspace \
+            -I "$index" \
+            -H 24 \
+            --fn 'monospace 10' \
+            --nf '#777777' \
+            --hb '#285577' \
+            --hf '#ffffff' \
+            --tf '#777777' \
+            --ff '#ffffff'
+    )
diff --git a/modules/workstation/windowing/sway/config.in b/modules/workstation/windowing/sway/config.in
new file mode 100644
index 000000000000..d59bdaa28db5
--- /dev/null
+++ b/modules/workstation/windowing/sway/config.in
@@ -0,0 +1,125 @@
+set $mod Mod4
+set $left h
+set $down j
+set $up k
+set $right l
+
+default_border pixel
+default_floating_border normal
+
+client.focused_inactive #333333 #5f676a #ffffff #484e50 #5f676a00
+client.unfocused        #333333 #222222 #888888 #292d2e #22222200
+
+for_window [app_id="float"] floating enable
+for_window [app_id="firefox" title="Picture-in-Picture"] floating enable
+for_window [class="Tor Browser"] floating enable
+for_window [class="XDvi"] floating enable
+for_window [instance="xdvi"] floating enable
+for_window [class="XDvi" instance="xdvi"] floating disable
+for_window [app_id="firefox" title="NoScript.*"] floating enable
+
+# Stop the Firefox sharing indicator (that appears when on an
+# audio/video call) tiling, or appearing in the center of the display,
+# or being focused.
+no_focus [title="(Firefox|Nightly) . Sharing Indicator"]
+for_window [title="(Firefox|Nightly) . Sharing Indicator"] {
+    floating enable
+    sticky enable
+
+    # I'd really like this to be at the top, horizontally centered.
+    # Or maybe at the bottom right.  But there's not really a way to
+    # do that in sway configuration, as far as I can tell.  I think
+    # I'd need to exec a program that would measure the display size
+    # and compute the coordinates or something.  That sounds horrible
+    # and fragile, so top left it is.
+    move position 0 0
+}
+
+input * natural_scroll enabled
+
+bindsym $mod+Return exec alacritty
+bindsym $mod+backslash exec firefox
+bindsym $mod+BackSpace kill
+bindsym $mod+d exec swaymsg exec "$(choosebin --tiebreak=begin,length,index)"
+
+# Brightness control
+bindsym $mod+F5 exec brightnessctl s 10%-
+bindsym $mod+F6 exec brightnessctl s 10%+
+
+# MPRIS
+bindsym $mod+F7 exec playerctl play-pause
+
+# PulseAudio
+bindsym $mod+F8 exec pactl set-sink-mute @DEFAULT_SINK@ toggle
+bindsym $mod+F9 exec pactl set-sink-volume @DEFAULT_SINK@ -5%
+bindsym $mod+F10 exec pactl set-sink-volume @DEFAULT_SINK@ +5%
+bindsym XF86AudioLowerVolume exec pactl set-sink-volume @DEFAULT_SINK@ -5%
+bindsym XF86AudioRaiseVolume exec pactl set-sink-volume @DEFAULT_SINK@ +5%
+
+# Drag floating windows by holding down $mod and left mouse button.
+# Resize them with right mouse button + $mod.
+# Despite the name, also works for non-floating windows.
+# Change normal to inverse to use left mouse button for resizing and right
+# mouse button for dragging.
+floating_modifier $mod normal
+
+# reload the configuration file
+bindsym $mod+Shift+c reload
+
+# exit sway (logs you out of your Wayland session)
+bindsym $mod+Shift+e exec swaynag -t warning -m 'You pressed the exit shortcut. Do you really want to exit sway? This will end your Wayland session.' -b 'Yes, exit sway' 'swaymsg exit'
+
+bindsym $mod+$left focus left
+bindsym $mod+$down focus down
+bindsym $mod+$up focus up
+bindsym $mod+$right focus right
+
+bindsym $mod+Shift+$left move left
+bindsym $mod+Shift+$down move down
+bindsym $mod+Shift+$up move up
+bindsym $mod+Shift+$right move right
+
+bindsym $mod+g exec swaymsg workspace "$(@choose_workspace@)"
+bindsym $mod+Shift+g exec swaymsg move container to workspace "$(@choose_workspace@)"
+
+bindsym $mod+b splith
+bindsym $mod+v splitv
+
+bindsym $mod+s layout stacking
+bindsym $mod+w layout tabbed
+bindsym $mod+e layout toggle split
+
+bindsym $mod+f fullscreen
+
+bindsym $mod+Shift+space floating toggle
+bindsym $mod+space focus mode_toggle
+
+bindsym $mod+a focus parent
+
+bindsym $mod+Shift+minus move scratchpad
+bindsym $mod+minus scratchpad show
+
+mode "resize" {
+    bindsym $left resize shrink width 10px
+    bindsym $down resize grow height 10px
+    bindsym $up resize shrink height 10px
+    bindsym $right resize grow width 10px
+
+    bindsym Return mode "default"
+    bindsym Escape mode "default"
+}
+bindsym $mod+r mode "resize"
+
+bar {
+    position top
+
+    status_command @status_command@
+
+    colors {
+        statusline #ffffff
+        background #00000077
+        inactive_workspace #33333377 #00000077 #FFFFFF77
+    }
+}
+
+@extraConfig@
diff --git a/modules/workstation/windowing/sway/default.nix b/modules/workstation/windowing/sway/default.nix
new file mode 100644
index 000000000000..0f2f7591b1a1
--- /dev/null
+++ b/modules/workstation/windowing/sway/default.nix
@@ -0,0 +1,47 @@
+{ pkgs, lib, config, ... }:
+
+let
+  inherit (lib) mdDoc mkOption optionalString;
+  inherit (lib.types) lines nullOr path;
+  inherit (pkgs) callPackage substituteAll;
+
+  cfg = config.programs.sway;
+in
+
+{
+  imports = [ ./swayidle ./swaylock ./wlsunset ./xdg-desktop-portal-wlr ];
+
+  options = {
+    programs.sway.extraConfig = mkOption {
+      type = lines;
+      description = mdDoc "Lines to append to sway's config file";
+      default = "";
+    };
+
+    programs.sway.wallpaper = mkOption {
+      type = nullOr path;
+      description = mdDoc "Path to wallpaper for sway and swaylock";
+      default = null;
+    };
+  };
+
+  config = {
+    environment.systemPackages = with pkgs; [ bemenu choose swayidle ];
+
+    programs.sway.enable = true;
+    programs.sway.wallpaper = callPackage ./wallpaper.nix { };
+    programs.sway.extraPackages = []; # extra packages can go in systemPackages.
+
+    programs.swayidle.enable = true;
+
+    users.users.qyliss.xdg.config.paths."sway/config" = substituteAll {
+      src = ./config.in;
+      choose_workspace =
+        "${callPackage ./choose_workspace.nix { }}/bin/choose_workspace";
+      status_command = "${callPackage ./status.nix { }}/bin/status";
+      extraConfig = cfg.extraConfig + optionalString (cfg.wallpaper != null) ''
+        output * bg ${cfg.wallpaper} fill
+      '';
+    };
+  };
+}
diff --git a/modules/workstation/windowing/sway/status.cpp b/modules/workstation/windowing/sway/status.cpp
new file mode 100644
index 000000000000..f6340f9f8e64
--- /dev/null
+++ b/modules/workstation/windowing/sway/status.cpp
@@ -0,0 +1,203 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+// Copyright 2020 Alyssa Ross
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 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 General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+#include <chrono>
+#include <fcntl.h>
+#include <filesystem>
+#include <iostream>
+#include <thread>
+#include <unistd.h>
+
+namespace fs = std::filesystem;
+
+using fs::directory_iterator;
+using std::chrono::seconds;
+using std::generic_category;
+using std::put_time;
+using std::stoi;
+using std::string;
+using std::stringstream;
+using std::system_error;
+using std::this_thread::sleep_for;
+using std::time;
+
+enum BatteryStatus {
+	Unknown,
+	Charging,
+	Discharging,
+	NotCharging,
+	Full,
+};
+
+class Battery {
+public:
+	Battery(string name);
+
+	string name() { return m_name; }
+	fs::path path();
+	BatteryStatus status();
+	int capacity();
+	int charge_now();
+
+private:
+	string m_name;
+	string attr(string name);
+};
+
+Battery::Battery(string name)
+{
+	m_name = name;
+}
+
+fs::path Battery::path()
+{
+	static fs::path base = "/sys/class/power_supply";
+	return base / name();
+}
+
+BatteryStatus Battery::status()
+{
+	auto status = attr("status");
+
+	if (status == "Charging")
+		return Charging;
+	if (status == "Discharging")
+		return Discharging;
+	if (status == "Not charging")
+		return NotCharging;
+	if (status == "Full")
+		return Full;
+
+	return Unknown;
+}
+
+int Battery::capacity()
+{
+	return stoi(attr("capacity"));
+}
+
+int Battery::charge_now()
+{
+	return stoi(attr("charge_now"));
+}
+
+string Battery::attr(string name)
+{
+	// Use read() to make sure this is done in a single read syscall,
+	// because sysfs doesn't like multiple reads.
+	int fd = open((path() / name).c_str(), O_RDONLY);
+	if (fd == -1)
+		throw system_error(errno, generic_category());
+	char buf[13];
+	int len = read(fd, &buf, 13);
+	if (len == -1)
+		throw system_error(errno, generic_category());
+	close(fd);
+	string value (buf, len);
+	if (value.back() == '\n')
+		value.pop_back();
+	return value;
+}
+
+int main(void)
+{
+	while (true) {
+		// Buffer output so it can be done all at once in a single write(),
+		// because of <https://github.com/swaywm/sway/issues/3857>.
+		stringstream out;
+
+		for (const auto& entry : directory_iterator("/sys/class/power_supply")) {
+			auto name = entry.path().filename().string();
+			if (name.find("BAT") != 0)
+				continue;
+
+			Battery battery (name);
+			auto batdisplay = false;
+
+			try {
+				switch (battery.status()) {
+				case Charging:
+					out << "↑";
+					break;
+				case Discharging:
+					out << "↓";
+					break;
+				default:
+					out << " ";
+				}
+				batdisplay = true;
+			} catch (const system_error& ex) {
+				switch (ex.code().value()) {
+				case ENOENT:
+					break;
+				case ENODEV:
+					out << "? ";
+					batdisplay = true;
+					break;
+				default:
+					throw ex;
+				}
+			}
+
+			try {
+				int capacity = battery.capacity();
+				out << capacity << "%";
+				batdisplay = true;
+			} catch (const system_error& ex) {
+				switch (ex.code().value()) {
+				case ENOENT:
+					break;
+				case ENODEV:
+					out << "??%";
+					batdisplay = true;
+					break;
+				default:
+					throw ex;
+				}
+
+				try {
+					int charge_now = battery.charge_now();
+					out << charge_now;
+					batdisplay = true;
+				} catch (const system_error& ex) {
+					switch (ex.code().value()) {
+					case ENOENT:
+						break;
+					case ENODEV:
+						out << "??????";
+						batdisplay = true;
+						break;
+					default:
+						throw ex;
+					}
+				}
+			}
+
+			if (batdisplay)
+				out << "  ";
+		}
+
+		auto t = time(nullptr);
+		out << put_time(localtime(&t), "%F %T") << "\n";
+
+		auto buf = out.str();
+		if (write(STDOUT_FILENO, buf.c_str(), buf.length()) == -1)
+			perror("write");
+
+		sleep_for(seconds(1));
+	}
+}
diff --git a/modules/workstation/windowing/sway/status.nix b/modules/workstation/windowing/sway/status.nix
new file mode 100644
index 000000000000..2697317d7611
--- /dev/null
+++ b/modules/workstation/windowing/sway/status.nix
@@ -0,0 +1,6 @@
+{ runCommandCC }:
+
+runCommandCC "status" {} ''
+  mkdir -p $out/bin
+  c++ -std=c++17 -o $out/bin/status ${./status.cpp}
+''
diff --git a/modules/workstation/windowing/sway/swayidle/default.nix b/modules/workstation/windowing/sway/swayidle/default.nix
new file mode 100644
index 000000000000..8e5f264b5038
--- /dev/null
+++ b/modules/workstation/windowing/sway/swayidle/default.nix
@@ -0,0 +1,23 @@
+{ lib, config, ... }:
+
+let
+  cfg = config.programs.swayidle;
+in
+
+with lib;
+
+{
+  options = {
+    programs.swayidle.enable = mkEnableOption "swayidle";
+  };
+
+  config = mkIf cfg.enable {
+    programs.sway.extraConfig = ''
+      exec swayidle \
+          timeout 300 'swaylock -c 000000' \
+          timeout 600 'swaymsg "output * dpms off"' \
+          resume 'swaymsg "output * dpms on"' \
+          before-sleep 'swaylock -c 000000'
+    '';
+  };
+}
diff --git a/modules/workstation/windowing/sway/swaylock/config.in b/modules/workstation/windowing/sway/swaylock/config.in
new file mode 100644
index 000000000000..c6f280aa8f5b
--- /dev/null
+++ b/modules/workstation/windowing/sway/swaylock/config.in
@@ -0,0 +1,3 @@
+image=@wallpaper@
+indicator-idle-visible
+show-failed-attempts
diff --git a/modules/workstation/windowing/sway/swaylock/default.nix b/modules/workstation/windowing/sway/swaylock/default.nix
new file mode 100644
index 000000000000..be15c87ade32
--- /dev/null
+++ b/modules/workstation/windowing/sway/swaylock/default.nix
@@ -0,0 +1,12 @@
+{ pkgs, config, ... }:
+
+{
+  imports = [ ../../../../xdg ];
+
+  environment.systemPackages = with pkgs; [ swaylock ];
+
+  users.users.qyliss.xdg.config.paths."swaylock/config" = pkgs.substituteAll {
+    src = ./config.in;
+    wallpaper = config.programs.sway.wallpaper;
+  };
+}
diff --git a/modules/workstation/windowing/sway/wallpaper.nix b/modules/workstation/windowing/sway/wallpaper.nix
new file mode 100644
index 000000000000..bed6bbe33a6b
--- /dev/null
+++ b/modules/workstation/windowing/sway/wallpaper.nix
@@ -0,0 +1,10 @@
+{ fetchurl }:
+
+fetchurl {
+  url = "https://mir-s3-cdn-cf.behance.net/project_modules/2800_opt_1/36731876964505.5c793fa788b5d.jpg";
+  sha256 = "1c6camdipng8ws41sgpcxzrxb96crgip3wirqjgf2ajn60qg3v64";
+
+  meta = {
+    homepage = "https://www.behance.net/gallery/76964505/IQOO-style-frame-and-scene-design";
+  };
+}
diff --git a/modules/workstation/windowing/sway/wlsunset/default.nix b/modules/workstation/windowing/sway/wlsunset/default.nix
new file mode 100644
index 000000000000..ba954f3cd3bd
--- /dev/null
+++ b/modules/workstation/windowing/sway/wlsunset/default.nix
@@ -0,0 +1,9 @@
+{ pkgs, ... }:
+
+{
+  environment.systemPackages = with pkgs; [ wlsunset ];
+  
+  programs.sway.extraConfig = ''
+    exec wlsunset -l 51.5 -L 13.6
+  '';
+}
diff --git a/modules/workstation/windowing/sway/xdg-desktop-portal-wlr/default.nix b/modules/workstation/windowing/sway/xdg-desktop-portal-wlr/default.nix
new file mode 100644
index 000000000000..9a63f5fce6c4
--- /dev/null
+++ b/modules/workstation/windowing/sway/xdg-desktop-portal-wlr/default.nix
@@ -0,0 +1,13 @@
+{ pkgs, ... }:
+
+{
+  xdg.portal.wlr.enable = true;
+
+  programs.sway.extraConfig = ''
+    exec ${pkgs.writeShellScript "sway-portal-environment" ''
+      dbus-update-activation-environment --systemd WAYLAND_DISPLAY XDG_CURRENT_DESKTOP
+      systemctl --user stop pipewire pipewire-media-session xdg-desktop-portal xdg-desktop-portal-wlr
+      systemctl --user start pipewire-media-session
+    ''}
+  '';
+}