Skip to content

Commit 14be86f

Browse files
committed
ci(workflow): add GitHub Actions workflow for building and releasing application
This commit introduces a new GitHub Actions workflow defined in `main.yml`, which automates the process of building the application using PyInstaller. The workflow includes steps for checking out the repository, setting up Python, installing dependencies, building the executable, compressing the output, generating a changelog, and creating a release on GitHub. Additionally, it adds a version file to manage application versioning and modifies the tray icon behavior to check for the existence of the built executable before deciding how to create a startup shortcut. This enhances the deployment process and ensures that the application can be easily built and released with minimal manual intervention.
1 parent 406650f commit 14be86f

File tree

5 files changed

+167
-23
lines changed

5 files changed

+167
-23
lines changed

.github/workflows/main.yml

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
name: Build with PyInstaller
2+
3+
on:
4+
workflow_dispatch:
5+
6+
jobs:
7+
build:
8+
runs-on: windows-latest
9+
steps:
10+
- name: Checkout repository
11+
uses: actions/checkout@v2
12+
- name: Set up Python
13+
uses: actions/setup-python@v2
14+
with:
15+
python-version: '3.12'
16+
- name: Install dependencies
17+
run: |
18+
python -m pip install --upgrade pip
19+
pip install pyinstaller
20+
pip install -r requirements.txt
21+
22+
- name: Build with PyInstaller
23+
run: |
24+
pyinstaller --noconfirm --onedir --console --icon "src/assets/favicon/app_icon.ico" --name "yasb" --contents-directory "libs" --clean --optimize "1" --version-file "src/version.txt" --bootloader-ignore-signals --hidden-import "core.widgets.yasb.power_menu" --hidden-import "core.widgets.yasb.volume" --hidden-import "core.widgets.yasb.weather" --hidden-import "core.widgets.yasb.memory" --hidden-import "core.widgets.yasb.cpu" --hidden-import "core.widgets.yasb.active_window" --hidden-import "core.widgets.yasb.applications" --hidden-import "core.widgets.yasb.battery" --hidden-import "core.widgets.yasb.clock" --hidden-import "core.widgets.yasb.custom" --hidden-import "core.widgets.yasb.github" --hidden-import "core.widgets.yasb.media" --hidden-import "core.widgets.yasb.wallpapers" --hidden-import "core.widgets.yasb.traffic" --hidden-import "core.widgets.komorebi.active_layout" --hidden-import "core.widgets.komorebi.workspaces" --hidden-import "core.widgets.yasb.wifi" --add-data "src/assets;assets/" "src/main.py"
25+
- name: Compress artifact
26+
run: |
27+
Compress-Archive -Path dist/yasb -DestinationPath dist/yasb_win.zip
28+
29+
- name: Get app version
30+
id: get_version
31+
run: |
32+
$version = (Select-String -Path "src/version.txt" -Pattern 'filevers=\(([0-9, ]+)\)' | ForEach-Object { $_.Matches.Groups[1].Value.Trim() -replace ' ', '' -replace ',', '.' })
33+
echo "VERSION=$version" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
34+
35+
- name: Create changelog text
36+
id: changelog
37+
uses: loopwerk/tag-changelog@v1
38+
with:
39+
token: ${{ secrets.PAT }}
40+
41+
- name: Create Release
42+
id: create_release
43+
uses: ncipollo/release-action@v1
44+
env:
45+
GITHUB_TOKEN: ${{ secrets.PAT }}
46+
with:
47+
artifacts: dist/yasb_win.zip
48+
tag: v${{ env.VERSION }}
49+
name: Release ${{ env.VERSION }}
50+
body: |
51+
Changelog for version ${{ env.VERSION }}
52+
${{ steps.changelog.outputs.changes }}
53+
draft: true
54+
prerelease: false
55+
56+
- name: Upload Release Asset
57+
uses: xresloader/upload-to-github-release@v1
58+
env:
59+
GITHUB_TOKEN: ${{ secrets.PAT }}
60+
with:
61+
file: "dist/yasb_win.zip"
62+
release_id: ${{ steps.create_release.outputs.id }}
63+
update_release_body: ${{ github.ref }}
64+
update_release_body_append: "true"
65+
draft: true
66+
overwrite: true
67+
prerelease: false

src/assets/favicon/app_icon.ico

187 KB
Binary file not shown.

src/core/tray.py

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,21 @@
1313
from settings import GITHUB_URL, SCRIPT_PATH, APP_NAME, DEFAULT_CONFIG_DIRECTORY
1414

1515
OS_STARTUP_FOLDER = os.path.join(os.environ['APPDATA'], r'Microsoft\Windows\Start Menu\Programs\Startup')
16-
AUTOSTART_FILE = os.path.join(SCRIPT_PATH, 'yasb.vbs')
17-
SHORTCUT_FILENAME = "yasb.lnk"
18-
16+
yasb_vbs_path = os.path.join(SCRIPT_PATH, 'yasb.vbs')
17+
18+
# Check if exe file exists, if exists
19+
yasb_exe_path = os.path.join(os.path.dirname(SCRIPT_PATH), 'yasb.exe')
20+
if os.path.exists(yasb_exe_path):
21+
print("yasb.exe exists in the directory.")
22+
SHORTCUT_FILENAME = "yasb.lnk"
23+
AUTOSTART_FILE = yasb_exe_path
24+
WORKING_DIRECTORY = os.path.dirname(SCRIPT_PATH)
25+
else:
26+
print("yasb.exe does not exist in the directory.")
27+
SHORTCUT_FILENAME = "yasb.lnk"
28+
AUTOSTART_FILE = yasb_vbs_path
29+
WORKING_DIRECTORY = SCRIPT_PATH
30+
1931
class TrayIcon(QSystemTrayIcon):
2032

2133
def __init__(self, bar_manager: BarManager):
@@ -107,7 +119,7 @@ def _enable_startup(self):
107119
try:
108120
with winshell.shortcut(shortcut_path) as shortcut:
109121
shortcut.path = AUTOSTART_FILE
110-
shortcut.working_directory = SCRIPT_PATH
122+
shortcut.working_directory = WORKING_DIRECTORY
111123
shortcut.description = "Shortcut to yasb.vbs"
112124
logging.info(f"Created shortcut at {shortcut_path}")
113125
except Exception as e:

src/core/widgets/yasb/volume.py

Lines changed: 47 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,43 @@
11
import re
2+
import ctypes
3+
import logging
24
from core.widgets.base import BaseWidget
35
from core.validation.widgets.yasb.volume import VALIDATION_SCHEMA
46
from PyQt6.QtWidgets import QLabel, QHBoxLayout, QWidget
5-
from PyQt6.QtCore import Qt
7+
from PyQt6.QtCore import Qt, pyqtSignal
68
from PyQt6.QtGui import QWheelEvent
7-
from comtypes import CLSCTX_ALL, CoInitialize, CoUninitialize
8-
from pycaw.pycaw import AudioUtilities, IAudioEndpointVolume
9-
import ctypes
10-
import logging
11-
9+
from comtypes import CLSCTX_ALL, CoInitialize, CoUninitialize, COMObject
10+
from pycaw.pycaw import AudioUtilities, IAudioEndpointVolume, IAudioEndpointVolumeCallback
11+
from pycaw.callbacks import MMNotificationClient
12+
1213
# Disable comtypes logging
1314
logging.getLogger('comtypes').setLevel(logging.CRITICAL)
1415

1516
# Constants from the Windows API
1617
VK_VOLUME_UP = 0xAF
1718
VK_VOLUME_DOWN = 0xAE
1819
KEYEVENTF_KEYUP = 0x0002
19-
UPDATE_INTERVAL = 1000
20-
20+
21+
class AudioEndpointChangeCallback(MMNotificationClient):
22+
def __init__(self,parent):
23+
super().__init__()
24+
self.parent = parent
25+
26+
def on_property_value_changed(self, device_id, property_struct, fmtid, pid):
27+
self.parent.update_label_signal.emit()
28+
29+
class AudioEndpointVolumeCallback(COMObject):
30+
_com_interfaces_ = [IAudioEndpointVolumeCallback]
31+
def __init__(self, parent):
32+
super().__init__()
33+
self.parent = parent
34+
def OnNotify(self, pNotify):
35+
self.parent.update_label_signal.emit()
36+
37+
2138
class VolumeWidget(BaseWidget):
2239
validation_schema = VALIDATION_SCHEMA
40+
update_label_signal = pyqtSignal()
2341

2442
def __init__(
2543
self,
@@ -28,7 +46,7 @@ def __init__(
2846
volume_icons: list[str],
2947
callbacks: dict[str, str]
3048
):
31-
super().__init__(UPDATE_INTERVAL, class_name="volume-widget")
49+
super().__init__(class_name="volume-widget")
3250
self._show_alt_label = False
3351
self._label_content = label
3452
self._label_alt_content = label_alt
@@ -49,10 +67,17 @@ def __init__(
4967
self.callback_left = "toggle_mute"
5068
self.callback_right = callbacks["on_right"]
5169
self.callback_middle = callbacks["on_middle"]
52-
self.callback_timer = "update_label"
53-
54-
self.start_timer()
70+
71+
self.cb = AudioEndpointChangeCallback(self)
72+
self.enumerator = AudioUtilities.GetDeviceEnumerator()
73+
self.enumerator.RegisterEndpointNotificationCallback(self.cb)
74+
75+
self._initialize_volume_interface()
76+
self.update_label_signal.connect(self._update_label)
77+
78+
self._update_label()
5579

80+
5681
def _toggle_label(self):
5782
self._show_alt_label = not self._show_alt_label
5883
for widget in self._widgets:
@@ -96,7 +121,6 @@ def _update_label(self):
96121
label_parts = re.split('(<span.*?>.*?</span>)', active_label_content)
97122
label_parts = [part for part in label_parts if part]
98123
widget_index = 0
99-
100124
try:
101125
self._initialize_volume_interface()
102126
mute_status = self.volume.GetMute()
@@ -130,13 +154,13 @@ def _get_volume_icon(self):
130154
self.setToolTip(f'Volume {current_volume_level}')
131155
if current_mute_status == 1:
132156
volume_icon = self._volume_icons[0]
133-
elif (current_volume_level >= 0 and current_volume_level < 11):
157+
elif 0 <= current_volume_level < 11:
134158
volume_icon = self._volume_icons[1]
135-
elif (current_volume_level >= 11 and current_volume_level < 30):
159+
elif 11 <= current_volume_level < 30:
136160
volume_icon = self._volume_icons[2]
137-
elif (current_volume_level >= 30 and current_volume_level < 60):
161+
elif 30 <= current_volume_level < 60:
138162
volume_icon = self._volume_icons[3]
139-
elif (current_volume_level >= 60):
163+
else:
140164
volume_icon = self._volume_icons[4]
141165
return volume_icon
142166

@@ -158,16 +182,20 @@ def wheelEvent(self, event: QWheelEvent):
158182
elif event.angleDelta().y() < 0:
159183
self._decrease_volume()
160184

185+
161186
def toggle_mute(self):
162187
current_mute_status = self.volume.GetMute()
163188
self.volume.SetMute(not current_mute_status, None)
164189
self._update_label()
165-
190+
191+
166192
def _initialize_volume_interface(self):
167193
CoInitialize()
168194
try:
169195
devices = AudioUtilities.GetSpeakers()
170196
interface = devices.Activate(IAudioEndpointVolume._iid_, CLSCTX_ALL, None)
171197
self.volume = interface.QueryInterface(IAudioEndpointVolume)
198+
self.callback = AudioEndpointVolumeCallback(self)
199+
self.volume.RegisterControlChangeNotify(self.callback)
172200
finally:
173-
CoUninitialize()
201+
CoUninitialize()

src/version.txt

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# UTF-8
2+
#
3+
# More details about fixed file info 'ffi':
4+
# http://msdn.microsoft.com/en-us/library/ms646997.aspx
5+
#
6+
VSVersionInfo(
7+
ffi=FixedFileInfo(
8+
filevers=(1, 0, 0, 0),
9+
prodvers=(1, 0, 0, 0),
10+
mask=0x3f,
11+
flags=0x0,
12+
OS=0x4,
13+
fileType=0x1,
14+
subtype=0x0,
15+
date=(0, 0)
16+
),
17+
kids=[
18+
StringFileInfo(
19+
[
20+
StringTable(
21+
'040904B0',
22+
[
23+
StringStruct('CompanyName', 'amnweb'),
24+
StringStruct('FileDescription', 'Yet Another Status Bar'),
25+
StringStruct('FileVersion', '1.0.0.0'),
26+
StringStruct('InternalName', 'yasb'),
27+
StringStruct('LegalCopyright', '© AmN. All rights reserved.'),
28+
StringStruct('OriginalFilename', 'yasb.exe'),
29+
StringStruct('ProductName', 'yasb'),
30+
StringStruct('ProductVersion', '1.0.0.0')
31+
]
32+
)
33+
]
34+
),
35+
VarFileInfo([VarStruct('Translation', [1033, 1200])])
36+
]
37+
)

0 commit comments

Comments
 (0)