Skip to content

Commit

Permalink
Merge branch 'kshift-cursor' of github.com:justjokiing/kshift into ks…
Browse files Browse the repository at this point in the history
…hift-cursor
  • Loading branch information
justjokiing committed Jan 17, 2025
2 parents fd02d97 + 6ec2b1c commit 621b58e
Show file tree
Hide file tree
Showing 5 changed files with 447 additions and 166 deletions.
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ https://github.com/user-attachments/assets/c18332df-b3b7-4bda-9254-e061a7fa0367

## Why Use kshift?

**Effortless and Dynamic Theme Automation**: Save time by automating theme changes for morning, evening, or specific events without manual adjustments. Coordinate your system's appearance with wallpapers, icon themes, and desktop themes that match the time of day or occasion.
**Effortless and Dynamic Theme Automation**: Save time by automating theme changes for morning, evening, or specific events without manual adjustments. Coordinate your system's appearance with colorschemes, wallpapers, cursorthemes, icon themes, and desktop themes that match the time of day or occasion.

**Customizable and Reliable Schedules**: Define unique schedules for themes using flexible time settings or solar events like sunrise and sunset. With systemd integration, theme changes are guaranteed to occur on time, even after system reboots.

Expand Down Expand Up @@ -94,9 +94,10 @@ All parameters are optional, but to enable automatic theme switching, a `time` v
| Parameter | Description | Example Value |
| -------------- | --------------------------------------------------- | --------------------------------- |
| `colorscheme` | Name of the Plasma color scheme | `BreezeLight` |
| `cursortheme` | Name of the Plasma cursor theme | `HighContrast` |
| `desktoptheme` | Name of the Plasma desktop theme | `Breeze` |
| `icontheme` | Name of the icon theme | `Papirus-Dark` |
| `wallpaper` | Path to the wallpaper image | `~/Pictures/morning.jpg` |
| `desktoptheme` | Name of the Plasma desktop theme | `Breeze` |
| `command` | Custom command to execute when the theme is applied | `echo 'Theme applied'` |
| `time` | Schedule for theme activation | `sunset`, `HH:MM`, `weekly` |

Expand All @@ -115,9 +116,10 @@ Run `kshift` with various options:
- `theme [THEME_NAME]`: Apply a specific theme by name. If no name is provided, kshift determines the appropriate theme to apply based on time and configuration.
- Positional argument `[THEME_NAME]`: Optional. Specify the theme to apply.
- `-c, --colorscheme <scheme>`: Apply a specific colorscheme (overrides the theme configuration).
- `-csr, --cursortheme <theme>`: Apply a specific cursor theme (overrides the theme configuration).
- `-dk, --desktop_theme <theme>`: Apply a specific desktop theme (overrides the theme configuration).
- `-i, --icontheme <theme>`: Apply a specific icon theme (overrides the theme configuration).
- `-w, --wallpaper <path>`: Apply a specific wallpaper (overrides the theme configuration).
- `-dk, --desktop_theme <theme>`: Apply a specific desktop theme (overrides the theme configuration).
- `install`: Install systemd services and timers for kshift.
- `remove`: Remove systemd services and timers for kshift.
- `status`: Display the current status of kshift and active timers.
Expand Down
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "hatchling.build"

[project]
name = "kshift"
version = "1.0.1"
version = "1.2.0"
authors = [
{ name="Seth Mackert", email="[email protected]" },
]
Expand All @@ -15,7 +15,7 @@ classifiers = [
"Programming Language :: Python",

# Development Status
"Development Status :: 4 - Beta", # If your project is stable, switch to "5 - Production/Stable"
"Development Status :: 5 - Production/Stable",

# Environment
"Environment :: X11 Applications :: KDE",
Expand Down
139 changes: 8 additions & 131 deletions src/kshift/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,136 +7,12 @@
import requests
import json

from kshift import utils
from kshift.theme import Theme

from pathlib import Path

from pydantic import BaseModel, Field, field_validator, model_validator
from typing import Dict, Optional, Union, List


class ThemeConfig(BaseModel):
# name: str = Field(description="Name for kshift theme")
colorscheme: Optional[str] = Field(
None, description="The name of the color scheme.")
icontheme: Optional[str] = Field(None,
description="The name of the icon theme.")
wallpaper: Optional[str] = Field(
None, description="Path to the wallpaper image.")
desktoptheme: Optional[str] = Field(None,
description="The desktop theme name.")
command: Optional[str] = Field(
None, description="Command to execute when the theme is applied.")
time: List[Union[str, datetime]] = Field(
[], description="The time when this theme is applied.")
enabled: bool = Field(True, description="Whether the theme is enabled.")

def kshift(self) -> None:

if self.wallpaper:
os.system(f"plasma-apply-wallpaperimage {self.wallpaper}")

if self.colorscheme and self.colorscheme != utils.curr_colorscheme():
os.system(f"plasma-apply-colorscheme {self.colorscheme}")

# if self.icontheme and self.icontheme != utils.curr_icontheme():
if self.icontheme:
plasma_changeicons = utils.find_plasma_changeicons()
if (plasma_changeicons is not None):
os.system(f"{plasma_changeicons} {self.icontheme}")

if self.desktoptheme and self.desktoptheme != utils.curr_desktoptheme(
):
os.system(f"plasma-apply-desktoptheme {self.desktoptheme}")

if self.command:
os.system(self.command)

@field_validator("colorscheme")
def validate_colorscheme(cls, value):
colorschemes = utils.get_colorschemes()
if value and value not in colorschemes:
raise ValueError(
f"Unknown colorscheme: {value}.\nValid options are {colorschemes}"
)
else:
return value

@field_validator("icontheme")
def validate_icontheme(cls, value):
iconthemes = utils.get_iconthemes()
if value and value not in iconthemes:
raise ValueError(
f"Unknown icontheme: {value}.\nValid options are {iconthemes}")
else:
return value

@field_validator("wallpaper")
def validate_wallpaper(cls, value):
if value:
value = Path(value).expanduser()
if value.exists():
return value
else:
raise ValueError(f"Wallpaper does not exist: {value}")

@field_validator("desktoptheme")
def validate_desktoptheme(cls, value):
desktopthemes = utils.get_desktopthemes()
if value and value not in desktopthemes:
raise ValueError(
f"Unknown desktoptheme: {value}.\nValid options are {desktopthemes}"
)
else:
return value

@field_validator("time", mode="before")
def parse_time(cls, value):
if isinstance(value, str):
return [value] # Convert single string to a list
elif isinstance(value, list):
if not all(isinstance(item, str) for item in value):
raise ValueError(
"All elements in the 'time' list must be strings.")
return value
else:
raise TypeError("'time' must be a string or a list of strings.")

@model_validator(mode="after")
def if_disabled(self):
if not self.enabled:
self.time = []

return self

@field_validator("time", mode="after")
def convert_time_strings(cls, times):
new_times = []
for item in times:
if isinstance(item, str):
if re.match(r'^\d{2}:\d{2}$', item):
# Parse "HH:MM" into a datetime object with today's date
now = datetime.now()
hour, minute = map(int, item.split(':'))
dt = now.replace(hour=hour,
minute=minute,
second=0,
microsecond=0)
# If the time has already passed today, schedule for tomorrow
if dt < now:
dt += timedelta(days=1)
item = dt

new_times.append(item)
elif isinstance(item, datetime):
new_times.append(item)
else:
raise TypeError(
"Each time entry must be a string in systemd OnCalendar format."
)

return new_times

from typing import Dict

defaults = {
"latitude": 39,
Expand All @@ -147,8 +23,8 @@ def convert_time_strings(cls, times):
"set_delay": 0,
"net_timeout": 10,
"themes": {
'day': ThemeConfig(colorscheme="BreezeLight", time=["sunrise"]),
'night': ThemeConfig(colorscheme="BreezeDark", time=["sunset"]),
'day': Theme(colorscheme="BreezeLight", time=["sunrise"]),
'night': Theme(colorscheme="BreezeDark", time=["sunset"]),
}
}

Expand All @@ -172,6 +48,7 @@ class Config(BaseModel):
sunset: datetime = Field(
datetime.strptime(defaults["sunset"], "%H:%M"),
description="Default sunset time in HH:MM format.")

rise_delay: int = Field(
defaults["rise_delay"],
ge=-23,
Expand All @@ -190,8 +67,8 @@ class Config(BaseModel):
ge=0,
le=60,
description="Network timeout in seconds, between 0 and 60.")
themes: Dict[str, ThemeConfig] = Field(defaults["themes"],
description="Dictionary of themes.")
themes: Dict[str, Theme] = Field(defaults["themes"],
description="Dictionary of themes.")

# Environment-based paths
home_directory: Path = Field(default=Path.home(),
Expand Down Expand Up @@ -229,7 +106,7 @@ class Config(BaseModel):
description="Path to the cache file for sunrise and sunset data.")

@model_validator(mode="after")
def set_dependant(self):
def set_dependant_paths(self):
# Compute dependent paths
self.systemd_loc = self.xdg_data / "systemd/user"
self.config_loc_base = self.xdg_config / "kshift"
Expand Down
81 changes: 51 additions & 30 deletions src/kshift/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@

from importlib.resources import files

from kshift.conf import ThemeConfig, load_config
from kshift.conf import load_config

from string import Template

import kshift.utils
from kshift.theme import *

c = load_config()

Expand Down Expand Up @@ -54,7 +54,7 @@ def log_theme_change(theme_name):
logging.info(json.dumps(log_data)) # Use JSON for structured logs


def log_element_change(theme: ThemeConfig):
def log_element_change(theme: Theme):
log_data = {"event": "specific_change", "theme": str(theme)}
logging.info(json.dumps(log_data))

Expand Down Expand Up @@ -311,26 +311,39 @@ def logs(all):

@cli.command(help="List possible themes or attributes")
@click.argument("attribute",
type=click.Choice(
["themes", "colorschemes", "iconthemes", "desktopthemes"],
case_sensitive=False))
type=click.Choice([
"themes", "colorschemes", "cursorthemes", "desktopthemes",
"iconthemes", "wallpapers"
],
case_sensitive=False))
def list(attribute):

def print_available(attr, items):
print(f"Available {attr}:")
for a in items:
print(f"- {a}")

if attribute == "themes":
print("Available themes:")
for name, theme_config in c.themes.items():
print(f"- {name}")
elif attribute == "colorschemes":
print("Available colorschemes:")
for colorscheme in kshift.utils.get_colorschemes():
print(f"- {colorscheme}")
elif attribute == "iconthemes":
print("Available icon themes:")
for icontheme in kshift.utils.get_iconthemes():
print(f"- {icontheme}")
elif attribute == "desktopthemes":
print("Available desktop themes:")
for desktop_theme in kshift.utils.get_desktopthemes():
print(f"- {desktop_theme}")
for name, conf in c.themes.items():
print(f"theme: {name}\n {conf}\n")
pass

items = []
match attribute:
# items are the class.available
case "colorschemes":
items = Colorscheme.fetch_colorschemes()[0]
case "cursorthemes":
items = CursorTheme.fetch_cursorthemes()[0]
case "desktopthemes":
items = DesktopTheme.fetch_desktopthemes()[0]
case "iconthemes":
items = IconTheme.fetch_iconthemes()[0]
case "wallpapers":
items = Wallpaper.fetch_wallpapers()[0]

if items:
print_available(attribute, items)


@cli.command(help="Change themes or apply specific theme elements")
Expand All @@ -340,12 +353,24 @@ def list(attribute):
default=None,
nargs=1,
metavar="[THEME_NAME]")
@click.option(
"-csr",
"--cursortheme",
type=str,
help="Set a specific cursor theme (overrides theme)",
)
@click.option(
"-c",
"--colorscheme",
type=str,
help="Set a specific colorscheme (overrides theme)",
)
@click.option(
"-dk",
"--desktop_theme",
type=str,
help="Set a specific desktop theme (overrides theme)",
)
@click.option(
"-i",
"--icontheme",
Expand All @@ -358,13 +383,8 @@ def list(attribute):
type=str,
help="Set a specific wallpaper (overrides theme)",
)
@click.option(
"-dk",
"--desktop_theme",
type=str,
help="Set a specific desktop theme (overrides theme)",
)
def theme(theme, colorscheme, icontheme, wallpaper, desktop_theme):
def theme(theme, colorscheme, cursortheme, desktop_theme, icontheme,
wallpaper):

kshift_status = subprocess.run(
"systemctl --user is-enabled kshift-startup.timer".split(),
Expand All @@ -378,10 +398,11 @@ def theme(theme, colorscheme, icontheme, wallpaper, desktop_theme):
else:
print(f"Error: Theme '{theme}' not found in configuration.")

if any([colorscheme, icontheme, wallpaper, desktop_theme]):
if any([colorscheme, cursortheme, desktop_theme, icontheme, wallpaper]):
# Apply individual theme elements dynamically
custom_theme = ThemeConfig(
custom_theme = Theme(
colorscheme=colorscheme,
cursortheme=cursortheme,
icontheme=icontheme,
wallpaper=wallpaper,
desktoptheme=desktop_theme,
Expand Down
Loading

0 comments on commit 621b58e

Please sign in to comment.