diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..30a66d0 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,22 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + + + + + + + { + "name": "Python Debugger: Current File with Arguments", + "type": "debugpy", + "request": "launch", + "program": "${file}", + "console": "integratedTerminal", + "args": "${command:pickArgs}" + } + ] +} \ No newline at end of file diff --git a/home-modules/hyprland/hyprland-autoname-workspaces.nix b/home-modules/hyprland-autoname-workspaces.nix similarity index 100% rename from home-modules/hyprland/hyprland-autoname-workspaces.nix rename to home-modules/hyprland-autoname-workspaces.nix diff --git a/home-modules/hyprland/hyprland.nix b/home-modules/hyprland.nix similarity index 97% rename from home-modules/hyprland/hyprland.nix rename to home-modules/hyprland.nix index dd7463e..4844a49 100644 --- a/home-modules/hyprland/hyprland.nix +++ b/home-modules/hyprland.nix @@ -50,42 +50,6 @@ in pkgs-unstable.hyprlock pkgs-unstable.hypridle - (pkgs.writeShellScriptBin "waybar_mode" '' - #!/bin/bash - - # Function to print help message - print_help() { - echo "Usage: waybar_mode {set |unset}" - } - if [ $# -lt 1 ]; then - print_help; exit 1; - fi - - case "$1" in - set) - # Check if there is a second argument for the 'set' operation - if [ $# -eq 2 ]; then - echo "$2" > /tmp/waybar-mode - pkill -RTMIN+8 waybar - else - echo "Error: 'set' operation requires exactly one string argument." - print_help - exit 1 - fi - ;; - unset) - echo "" > /tmp/waybar-mode - pkill -RTMIN+8 waybar - ;; - *) - echo "Error: Unknown command '$1'" - print_help - exit 1 - ;; - esac - - exit 0 - '') ]; wayland.windowManager.hyprland = { diff --git a/home-modules/nx-gcal-event.nix b/home-modules/nx-gcal-event.nix new file mode 100644 index 0000000..6a7effe --- /dev/null +++ b/home-modules/nx-gcal-event.nix @@ -0,0 +1,222 @@ +{ config, pkgs, secrets, ... }: +let in +{ + home = { + file."${config.xdg.dataHome}/nx-gcal-event-credentials.json".text = '' + { + "installed": { + "client_id": "${secrets.nx-gcal-event.client-client-id}", + "project_id": "my-own-cal", + "auth_uri": "https://accounts.google.com/o/oauth2/auth", + "token_uri": "https://oauth2.googleapis.com/token", + "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", + "client_secret": "${secrets.nx-gcal-event.client-secret}", + "redirect_uris": [ + "http://localhost" + ] + } + } + ''; + + packages = with pkgs; [ + # TODO: make into real package, currently dependencies are in home.nix + # (pkgs.python311.withPackages (python-pkgs: [ + # python-pkgs.google + # ])) + (writeScriptBin "nx_gcal_event" '' + #!${pkgs.python3}/bin/python3 + import datetime + import os + import pickle + import sys + + from google.auth.transport.requests import Request + from google.oauth2.credentials import Credentials + from google_auth_oauthlib.flow import InstalledAppFlow + from googleapiclient.discovery import build + from googleapiclient.errors import HttpError + from html import escape + + + + CREDENTIALS_PATH = f"{os.environ['XDG_DATA_HOME']}/nx-gcal-event-credentials.json" + TOKEN_PATH = f"{os.environ['XDG_CACHE_HOME']}/nx-gcal-event-token.json" + PICKLE_PATH = "/tmp/nx-gcal-event.pickle" + + + def sec_to_nice_string(seconds: int): + (hours, rsec) = divmod(seconds, 3600) + minutes = rsec // 60 + sep = " " + if hours == 0: + s_hours = f"" + sep = "" + elif hours == 1: s_hours = f"{hours} hour" + else: s_hours = f"{hours} hours" + if minutes == 0: + s_minutes = f"" + sep = "" + elif minutes == 1: s_minutes = f"{minutes} minute" + else: s_minutes = f"{minutes} minutes" + if hours + minutes == 0: + s_minutes = "~ No time" + os.remove(PICKLE_PATH) + + return f"{s_hours}{sep}{s_minutes}" + + def get_event_from_api(): + creds = None + SCOPES = ["https://www.googleapis.com/auth/calendar.readonly"] + + if os.path.exists(TOKEN_PATH): + creds = Credentials.from_authorized_user_file(TOKEN_PATH, SCOPES) + + if not creds or not creds.valid: + if creds and creds.expired and creds.refresh_token: + creds.refresh(Request()) + else: + flow = InstalledAppFlow.from_client_secrets_file(CREDENTIALS_PATH, SCOPES) + creds = flow.run_local_server(port=0) + # Save the credentials for the next run + with open(TOKEN_PATH, "w") as token: + token.write(creds.to_json()) + + try: + service = build("calendar", "v3", credentials=creds) + now = datetime.datetime.utcnow().isoformat() + "Z" # 'Z' indicates UTC time + in_24_h = (datetime.datetime.utcnow() + datetime.timedelta(days=1)).isoformat() + "Z" + calendar_list = service.calendarList().list().execute() + calendars = calendar_list.get("items", []) + + # List events from all calendars + all_events = [] + for calendar in calendars: + calendar_id = calendar["id"] + events_result = service.events().list( + calendarId=calendar_id, + timeMin=now, + timeMax=in_24_h, + singleEvents=True, + orderBy="startTime", + ).execute() + events = events_result.get("items", []) + + all_events.extend(events) + + # Filter out all-day events + all_events = [event for event in all_events if "dateTime" in event["start"]] + + # Find the earliest event + earliest_event = None + for event in all_events: + event_start = event["start"]["dateTime"] + if not earliest_event or event_start < earliest_event["start"]["dateTime"]: + earliest_event = event + + # Now earliest_event contains the event that starts earliest + return earliest_event + + except HttpError as error: + print("An error occurred: %s" % error) + exit(1) + + def dump_dict_to_file(event, now): + if not event: + event = {} + event['nxWriteTime'] = now + with open(PICKLE_PATH, 'wb') as f: + pickle.dump(event, f) + + def load_dict_from_file(now): + with open(PICKLE_PATH, 'rb') as f: + event = pickle.load(f) + # recheck all 15 minutes + if (now - event['nxWriteTime']).seconds > 900: + event = get_event_from_api() + os.remove(PICKLE_PATH) + return event + + def lookup(): + # set now (timezone CEST) + now = datetime.datetime.now(datetime.timezone(datetime.timedelta(hours=2))) + + no_event_string = "~ Zen ~" + + if os.path.exists(PICKLE_PATH): + event = load_dict_from_file(now) + else: + event = get_event_from_api() + if not event: + print(no_event_string) + dump_dict_to_file(event, now) + exit(0) + dump_dict_to_file(event, now) + + try: # when the saved even was empty (there was no event) + end = datetime.datetime.strptime(event["end"]["dateTime"], "%Y-%m-%dT%H:%M:%S%z") + start = datetime.datetime.strptime(event["start"]["dateTime"], "%Y-%m-%dT%H:%M:%S%z") + except: + if (now - event['nxWriteTime']).seconds > 900: + event = get_event_from_api() + os.remove(PICKLE_PATH) + else: + print(no_event_string) + exit(0) + + # set mode, remaining + if start.day != now.day: # event start tomorrow + print(no_event_string) + exit(0) + elif (start - now).days < 0: # today, started alredy + remaining = end - now + mode = " remaining in " + else: # today, not started yet + remaining = start - now + mode = " until the start of " + + name = escape(event['summary']) + + print(f"󱙬 {sec_to_nice_string(remaining.seconds)}{mode}\'{name}\'") + exit(0) + + def print_help(): + print("Usage: nx_gcal_event [lookup|force-lookup|reauthenicate|help]") + + def forece_lookup(): + try: + os.remove(PICKLE_PATH) + os.system('notify-send --app-name="nx_gcal_event" "Saved event deleted!"') + except: + os.system('notify-send --app-name="nx_gcal_event" "No saved event found!"') + finally: + lookup() + + def reauthenicate(): + os.remove(PICKLE_PATH) + os.remove(TOKEN_PATH) + os.system('notify-send --app-name="nx_gcal_event" "Deleted Token"') + lookup() + + def print_help(): + print("Usage: nx_gcal_event [lookup|force-lookup|reauthenicate|help]") + + if __name__ == "__main__": + if len(sys.argv) != 2: + print("Incorrect number of arguments.") + print_help() + else: + arg = sys.argv[1] + if arg == "lookup": + lookup() + elif arg == "force-lookup": + forece_lookup() + elif arg == "reauthenticate": + reauthenicate() + elif arg == "help": + print_help() + else: + print_help() + '') + ]; + }; +} diff --git a/home-modules/python.nix b/home-modules/python.nix new file mode 100644 index 0000000..4d53a84 --- /dev/null +++ b/home-modules/python.nix @@ -0,0 +1,18 @@ +{ config, pkgs, ... }: +let + python-with-packages = pkgs.python3.withPackages (pp: with pp; [ + ipython + pipdeptree + requests + google google-api-python-client google-auth-httplib2 google-auth-oauthlib + ]); +in +{ + home.packages = [ + python-with-packages + ]; + + home.sessionVariables = { + PYTHONPATH = "${python-with-packages}/${python-with-packages.sitePackages}"; + }; +} \ No newline at end of file diff --git a/home-modules/hyprland/waybar.nix b/home-modules/waybar.nix similarity index 71% rename from home-modules/hyprland/waybar.nix rename to home-modules/waybar.nix index fab7142..292d2bf 100644 --- a/home-modules/hyprland/waybar.nix +++ b/home-modules/waybar.nix @@ -2,8 +2,56 @@ let in { + imports = [ + ./nx-gcal-event.nix + ]; + home.packages = with pkgs; [ waybar + + (pkgs.writeShellScriptBin "waybar_mode" '' + #!/usr/bin/env bash + print_help() { + echo "Usage: waybar_mode {set |unset}" + } + if [ $# -lt 1 ]; then + print_help; exit 1; + fi + case "$1" in + set) + # Check if there is a second argument for the 'set' operation + if [ $# -eq 2 ]; then + echo "$2" > /tmp/waybar-mode + pkill -RTMIN+8 waybar + else + echo "Error: 'set' operation requires exactly one string argument." + print_help + exit 1 + fi + ;; + unset) + echo "" > /tmp/waybar-mode + pkill -RTMIN+8 waybar + ;; + *) + echo "Error: Unknown command '$1'" + print_help + exit 1 + ;; + esac + exit 0 + '') + (pkgs.writeShellScriptBin "cclock" '' + #!/bin/bash + #ord=$(date +"%e" | awk '{printf("%d%s\n", $1, substr("thstndrd", ($1%100-20)%10*2+1, 2))}') + ord=$(date +"%e" | awk '{printf("%d%s\n", $1, ($1==11||$1==12||$1==13)?"th":((($1%10)==1)?"st":((($1%10)==2)?"nd":((($1%10)==3)?"rd":"th"))))}') + if [ $# -eq 0 ]; then + echo "󰃮 $(date +'%A the')" "$ord" "of" "$(date +'%B')" "  " "$(date +'%R')" + elif [ "$1" = "--no-icons" ]; then + echo "$(date +'%A the')" "$ord" "of" "$(date +'%B')" "$(date +'%R')" + fi + '') + ]; programs.waybar = { @@ -48,11 +96,11 @@ in separate-outputs = true; }; "custom/cclock" = { - exec = "/home/nx2/scripts/cclock.sh"; + exec = "cclock"; restart-interval = 60; }; "custom/ctimeremaining" = { - exec = "python /home/nx2/scripts/NxGCalEvent/get-remaining-time.py"; + exec = "nx_gcal_event lookup"; restart-interval = 60; }; "custom/mode" = { diff --git a/home.nix b/home.nix index af1e6ff..7e0d3dc 100644 --- a/home.nix +++ b/home.nix @@ -14,9 +14,9 @@ ./home-modules/pnx/pnx.nix # ./home-modules/hsmw.nix - ./home-modules/hyprland/hyprland.nix - ./home-modules/hyprland/hyprland-autoname-workspaces.nix - ./home-modules/hyprland/waybar.nix + ./home-modules/hyprland.nix + ./home-modules/hyprland-autoname-workspaces.nix + ./home-modules/waybar.nix ./home-modules/wlogout.nix ./home-modules/kitty.nix @@ -31,6 +31,8 @@ ./home-modules/theme/gtk.nix ./home-modules/theme/qt.nix + + ./home-modules/python.nix ]; home.username = "nx2"; home.homeDirectory = "/home/nx2"; @@ -66,18 +68,6 @@ gnumake speedtest-go - - (pkgs.python3.withPackages (python-pkgs: [ - python-pkgs.ipython - python-pkgs.pipdeptree - python-pkgs.requests - ])) - # (writeShellScriptBin "nxrbs-nix" '' - # set -e - # pushd ~/.nix-dots/ - # git diff - # '') - ]; xdg = { diff --git a/secrets/passwords-and-certificates.nix b/secrets/passwords-and-certificates.nix index 80b568a..40e0a49 100644 Binary files a/secrets/passwords-and-certificates.nix and b/secrets/passwords-and-certificates.nix differ