Skip to content

Commit ff805d5

Browse files
authored
Add files via upload
1 parent 0da3735 commit ff805d5

File tree

4 files changed

+130
-4
lines changed

4 files changed

+130
-4
lines changed

swap-cli.py

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import os
2+
import sys
3+
import json
4+
import subprocess
5+
import psutil
6+
import re
7+
8+
def get_base_path():
9+
if getattr(sys, 'frozen', False):
10+
return os.path.dirname(sys.executable)
11+
else:
12+
return os.path.dirname(os.path.abspath(__file__))
13+
14+
BASE_DIR_SETTINGS = get_base_path()
15+
PROFILE_FILE = os.path.join(BASE_DIR_SETTINGS, "audio_profiles.json")
16+
PROFILE_NAME_REGEX = re.compile(r'^[A-Za-z0-9-]+$')
17+
18+
class AudioProfileManager:
19+
def __init__(self):
20+
self.profiles = {}
21+
self.soundvolumeview_path = "SoundVolumeView.exe"
22+
self.config_file = PROFILE_FILE
23+
24+
def load_config(self):
25+
if os.path.exists(self.config_file):
26+
with open(self.config_file, 'r') as f:
27+
config = json.load(f)
28+
if not isinstance(config, dict) or 'profiles' not in config or not isinstance(config['profiles'], dict):
29+
raise ValueError("Not a valid audio_profiles.json")
30+
self.profiles = config.get('profiles', {})
31+
self.soundvolumeview_path = config.get('soundvolumeview_path', self.soundvolumeview_path)
32+
33+
def apply_profile(self, profile_name):
34+
if profile_name not in self.profiles:
35+
return 0
36+
rules = self.profiles[profile_name]['rules']
37+
applied_count = 0
38+
for rule in rules:
39+
if self.execute_rule(rule):
40+
applied_count += 1
41+
return applied_count
42+
43+
def execute_rule(self, rule):
44+
try:
45+
if not any(p.name().lower() == rule['app_name'].lower() for p in psutil.process_iter(['name'])):
46+
return False
47+
cmd = [
48+
self.soundvolumeview_path,
49+
'/SetAppDefault',
50+
rule['device_id'],
51+
'1',
52+
rule['app_name']
53+
]
54+
subprocess.run(cmd, check=True, capture_output=True)
55+
return True
56+
except Exception:
57+
return False
58+
59+
if __name__ == "__main__":
60+
import argparse
61+
62+
parser = argparse.ArgumentParser(description="SmartWindowsAudioProfiles CLI")
63+
parser.add_argument("profile_name", help="Profile name to activate (uses audio_profiles.json in app directory)")
64+
args = parser.parse_args()
65+
66+
if not PROFILE_NAME_REGEX.match(args.profile_name):
67+
print("ERROR: Invalid profile name! Only letters, numbers, and hyphens (-) are allowed. No spaces.")
68+
sys.exit(1)
69+
70+
app = AudioProfileManager()
71+
try:
72+
if not os.path.exists(PROFILE_FILE):
73+
print("ERROR: audio_profiles.json not found in the application directory.")
74+
sys.exit(1)
75+
app.load_config()
76+
if args.profile_name in app.profiles:
77+
applied = app.apply_profile(args.profile_name)
78+
if applied > 0:
79+
print(f"Profile '{args.profile_name}' activated with {applied} rule(s).")
80+
sys.exit(0)
81+
else:
82+
print(f"ERROR: Profile '{args.profile_name}' found but no rules could be applied. Are the target applications running? Is SoundVolumeView.exe configured correctly?")
83+
sys.exit(2)
84+
else:
85+
print(f"ERROR: Profile '{args.profile_name}' not found in audio_profiles.json.")
86+
sys.exit(1)
87+
except Exception as e:
88+
print(f"ERROR: Failed to activate profile '{args.profile_name}': {e}")
89+
sys.exit(2)

swap-cli.spec

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# swap-cli.spec
2+
block_cipher = None
3+
4+
a = Analysis(
5+
['swap-cli.py'],
6+
pathex=[],
7+
binaries=[],
8+
datas=[('icon.ico', '.')],
9+
hiddenimports=['psutil'],
10+
hookspath=[],
11+
runtime_hooks=[],
12+
excludes=[],
13+
win_no_prefer_redirects=False,
14+
win_private_assemblies=False,
15+
cipher=block_cipher,
16+
)
17+
18+
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
19+
20+
exe = EXE(
21+
pyz,
22+
a.scripts,
23+
a.binaries,
24+
a.zipfiles,
25+
a.datas,
26+
[],
27+
name='SWAP-cli',
28+
debug=False,
29+
bootloader_ignore_signals=False,
30+
strip=False,
31+
upx=True,
32+
console=True,
33+
icon='icon.ico'
34+
)

swap.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -400,21 +400,25 @@ def new_profile(self):
400400
else:
401401
self.changes_pending = True
402402
self.mark_profiles_tab_unsaved()
403-
403+
404+
404405
def delete_profile(self):
405406
current = self.profile_var.get()
407+
print(f"Before deletion: {list(self.profiles.keys())}")
406408
if current == "Select a profile...":
407409
messagebox.showwarning("Warning", "Please select a valid profile.", parent=self.root)
408410
return
409411
if not current:
410412
messagebox.showwarning("Warning", "No profile selected!", parent=self.root)
411413
return
412-
414+
413415
if messagebox.askyesno("Confirm", f"Delete profile '{current}'?"):
414416
del self.profiles[current]
417+
print(f"After deletion: {list(self.profiles.keys())}")
415418
self.update_profile_combo()
416419
self.profile_var.set("Select a profile...")
417420
self.on_profile_selected()
421+
418422
if getattr(self, 'auto_save_var', True) and self.auto_save_var.get():
419423
self.save_config()
420424
self.changes_pending = False
@@ -560,7 +564,6 @@ def delete_rule(self):
560564

561565

562566
def on_profile_selected(self, event=None):
563-
self.load_config()
564567
selected = self.profile_var.get()
565568
if selected == "Select a profile..." or selected not in self.profiles:
566569
self.rules_listbox.delete(0, tk.END)

swap.spec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ exe = EXE(
2424
a.zipfiles,
2525
a.datas,
2626
[],
27-
name='SmartWindowsAudioProfiles',
27+
name='SWAP',
2828
debug=False,
2929
bootloader_ignore_signals=False,
3030
strip=False,

0 commit comments

Comments
 (0)