9 Commits

Author SHA1 Message Date
e4b3fae4bf Implement multi instance support 2026-01-19 21:35:46 +01:00
a24aef7f0a Update 2026-01-19 19:24:10 +01:00
3bf7355017 Add drones profile 2026-01-19 19:23:58 +01:00
702c267f90 Update 2026-01-19 12:28:21 +01:00
6a128ef899 Update 2026-01-17 12:38:01 +01:00
2ae6a90117 Update configs 2026-01-17 12:33:05 +01:00
1b334edaf7 Add profiles 2026-01-17 12:27:26 +01:00
0a920a0c8d Implement a simple profile manager and allow for switching between
profiles via tray icon
2026-01-17 12:27:16 +01:00
b50491a08d Fix issue where hiding thumbnails was delayed 2026-01-16 23:35:50 +01:00
20 changed files with 1541 additions and 22 deletions

View File

@@ -0,0 +1,10 @@
{
"ActiveProfile": "capacitor",
"Profiles": [
"Default",
"capacitor",
"drones",
"target-1row",
"target-3row"
]
}

View File

@@ -80,7 +80,7 @@
"EnablePerClientThumbnailLayouts": false,
"HideThumbnailsOnLostFocus": false,
"HideThumbnailsDelay": 2,
"ThumbnailSize": "429, 140",
"ThumbnailSize": "512, 288",
"ThumbnailMaximumSize": "960, 540",
"ThumbnailMinimumSize": "192, 108",
"EnableThumbnailSnap": true,
@@ -93,14 +93,14 @@
"ShowThumbnailFrames": false,
"LockThumbnailLocation": false,
"ThumbnailSnapToGrid": true,
"ThumbnailSnapToGridSizeX": 107,
"ThumbnailSnapToGridSizeY": 35,
"ThumbnailSnapToGridSizeX": 64,
"ThumbnailSnapToGridSizeY": 36,
"EnableActiveClientHighlight": true,
"ActiveClientHighlightColor": "GreenYellow",
"OverlayLabelColor": "Orange",
"OverlayLabelSize": 10,
"EnableThumbnailRegionSnipping": true,
"DefaultThumbnailRegion": "1205, 1269, 429, 140",
"EnableThumbnailRegionSnipping": false,
"DefaultThumbnailRegion": "1664, 188, 451, 238",
"CurrentProfile": "Default",
"AvailableProfiles": [
"Default"
@@ -114,15 +114,23 @@
"ThumbnailsManuallyHidden": false,
"PerClientLayout": {},
"FlatLayout": {
"EVE - Quartio": "0, 1295",
"EVE - Tertiale": "0, 1155",
"EVE - Secundamen": "0, 1015",
"EVE - Primorium": "0, 875",
"EVE - PhatPhuckDave": "0, 735"
"EVE - Quartio": "-896, 1620",
"EVE - Tertiale": "-640, 1332",
"EVE - PhatPhuckDave": "-1152, 1044",
"EVE - Primorium": "-640, 1044",
"EVE - Secundamen": "-1152, 1332",
"EVE - Quartomo": "-512, 1152",
"EVE - Tertiashi": "-512, 864",
"EVE - Secundaru": "-512, 576",
"EVE - Primosaki": "-512, 288",
"EVE - PhuckingWeebDave": "-512, 0",
"EVE": "0, 0"
},
"ClientLayout": {},
"ClientHotkey": {},
"DisableThumbnail": {},
"DisableThumbnail": {
"EVE - PhatPhuckDave (1)": false
},
"PriorityClients": [],
"ExecutablesToPreview": [
"exefile"

View File

@@ -0,0 +1,135 @@
{
"ConfigVersion": 1,
"CycleGroup1ForwardHotkeys": [
"F14",
"Control+F14"
],
"CycleGroup1BackwardHotkeys": [
"F13",
"Control+F13"
],
"CycleGroup1ClientsOrder": {
"EVE - Example DPS Toon 1": 1,
"EVE - Example DPS Toon 2": 2,
"EVE - Example DPS Toon 3": 3
},
"CycleGroup2ForwardHotkeys": [
"F16",
"Control+F16"
],
"CycleGroup2BackwardHotkeys": [
"F15",
"Control+F15"
],
"CycleGroup2ClientsOrder": {
"EVE - Example Logi Toon 1": 1,
"EVE - Example Scout Toon 2": 2,
"EVE - Example Tackle Toon 3": 3
},
"CycleGroup3ForwardHotkeys": [
""
],
"CycleGroup3BackwardHotkeys": [
""
],
"CycleGroup3ClientsOrder": {
"EVE - cycle group 3": 1
},
"CycleGroup4ForwardHotkeys": [
""
],
"CycleGroup4BackwardHotkeys": [
""
],
"CycleGroup4ClientsOrder": {
"EVE - cycle group 4": 1
},
"CycleGroup5ForwardHotkeys": [
""
],
"CycleGroup5BackwardHotkeys": [
""
],
"CycleGroup5ClientsOrder": {
"EVE - cycle group 5": 1
},
"PerClientActiveClientHighlightColor": {
"EVE - Example Toon 1": "Red",
"EVE - Example Toon 2": "Green"
},
"PerClientThumbnailSize": {
"EVE - Example Toon 1": "200, 200",
"EVE - Example Toon 2": "200, 200"
},
"PerClientThumbnailRegion": {},
"PerClientZoomAnchor": {
"EVE - Example Toon 1": 1,
"EVE - Example Toon 2": 7
},
"MinimizeToTray": true,
"ThumbnailRefreshPeriod": 500,
"ThumbnailResizeTimeoutPeriod": 500,
"WineCompatibilityMode": false,
"ThumbnailsOpacity": 1.0,
"EnableClientLayoutTracking": false,
"HideActiveClientThumbnail": false,
"HideLoginClientThumbnail": false,
"MinimizeInactiveClients": false,
"WindowsAnimationStyle": 1,
"ShowThumbnailsAlwaysOnTop": true,
"EnablePerClientThumbnailLayouts": false,
"HideThumbnailsOnLostFocus": false,
"HideThumbnailsDelay": 2,
"ThumbnailSize": "451, 238",
"ThumbnailMaximumSize": "960, 540",
"ThumbnailMinimumSize": "192, 108",
"EnableThumbnailSnap": true,
"ThumbnailSnapRange": 150,
"EnableThumbnailZoom": false,
"ThumbnailZoomFactor": 2,
"ThumbnailZoomAnchor": 0,
"OverlayLabelAnchor": 0,
"ShowThumbnailOverlays": true,
"ShowThumbnailFrames": false,
"LockThumbnailLocation": false,
"ThumbnailSnapToGrid": true,
"ThumbnailSnapToGridSizeX": 112,
"ThumbnailSnapToGridSizeY": 59,
"EnableActiveClientHighlight": true,
"ActiveClientHighlightColor": "GreenYellow",
"OverlayLabelColor": "Orange",
"OverlayLabelSize": 10,
"EnableThumbnailRegionSnipping": true,
"DefaultThumbnailRegion": "1664, 188, 451, 238",
"CurrentProfile": "Default",
"ProfileReadOnly": false,
"IconName": "IconOriginal",
"ActiveClientHighlightThickness": 3,
"LoginThumbnailLocation": "5, 5",
"ToggleTrackingHotkey": "Alt+F16",
"ToggleSingleProcessHotkey": "Control+F16",
"ToggleAllThumbnailsHotkey": "Shift+Alt+Oem3",
"ThumbnailsManuallyHidden": false,
"PerClientLayout": {},
"FlatLayout": {
"EVE - Sn v1 cosunoo (2)": "1995, 1058",
"EVE - Primorium (1)": "240, 645",
"EVE - PhatPhuckDave (3)": "224, -2",
"EVE - Primorium (3)": "146, 285",
"EVE - PhatPhuckDave (2)": "146, 57",
"EVE - Sn v1 cosunoo (1)": "154, 472",
"EVE - Sn v1 cosunoo (3)": "292, 517",
"EVE - Primorium (2)": "224, 236",
"EVE - PhatPhuckDave (1)": "292, 57",
"EVE - Secundamen (1)": "224, 474"
},
"ClientLayout": {},
"ClientHotkey": {},
"DisableThumbnail": {
"EVE - PhatPhuckDave (1)": false
},
"PriorityClients": [],
"ExecutablesToPreview": [
"exefile"
]
}

View File

@@ -0,0 +1,135 @@
{
"ConfigVersion": 1,
"CycleGroup1ForwardHotkeys": [
"F14",
"Control+F14"
],
"CycleGroup1BackwardHotkeys": [
"F13",
"Control+F13"
],
"CycleGroup1ClientsOrder": {
"EVE - Example DPS Toon 1": 1,
"EVE - Example DPS Toon 2": 2,
"EVE - Example DPS Toon 3": 3
},
"CycleGroup2ForwardHotkeys": [
"F16",
"Control+F16"
],
"CycleGroup2BackwardHotkeys": [
"F15",
"Control+F15"
],
"CycleGroup2ClientsOrder": {
"EVE - Example Logi Toon 1": 1,
"EVE - Example Scout Toon 2": 2,
"EVE - Example Tackle Toon 3": 3
},
"CycleGroup3ForwardHotkeys": [
""
],
"CycleGroup3BackwardHotkeys": [
""
],
"CycleGroup3ClientsOrder": {
"EVE - cycle group 3": 1
},
"CycleGroup4ForwardHotkeys": [
""
],
"CycleGroup4BackwardHotkeys": [
""
],
"CycleGroup4ClientsOrder": {
"EVE - cycle group 4": 1
},
"CycleGroup5ForwardHotkeys": [
""
],
"CycleGroup5BackwardHotkeys": [
""
],
"CycleGroup5ClientsOrder": {
"EVE - cycle group 5": 1
},
"PerClientActiveClientHighlightColor": {
"EVE - Example Toon 1": "Red",
"EVE - Example Toon 2": "Green"
},
"PerClientThumbnailSize": {
"EVE - Example Toon 1": "200, 200",
"EVE - Example Toon 2": "200, 200"
},
"PerClientThumbnailRegion": {},
"PerClientZoomAnchor": {
"EVE - Example Toon 1": 1,
"EVE - Example Toon 2": 7
},
"MinimizeToTray": true,
"ThumbnailRefreshPeriod": 500,
"ThumbnailResizeTimeoutPeriod": 500,
"WineCompatibilityMode": false,
"ThumbnailsOpacity": 1.0,
"EnableClientLayoutTracking": false,
"HideActiveClientThumbnail": false,
"HideLoginClientThumbnail": false,
"MinimizeInactiveClients": false,
"WindowsAnimationStyle": 1,
"ShowThumbnailsAlwaysOnTop": true,
"EnablePerClientThumbnailLayouts": false,
"HideThumbnailsOnLostFocus": false,
"HideThumbnailsDelay": 2,
"ThumbnailSize": "429, 140",
"ThumbnailMaximumSize": "960, 540",
"ThumbnailMinimumSize": "192, 108",
"EnableThumbnailSnap": true,
"ThumbnailSnapRange": 0,
"EnableThumbnailZoom": false,
"ThumbnailZoomFactor": 2,
"ThumbnailZoomAnchor": 0,
"OverlayLabelAnchor": 0,
"ShowThumbnailOverlays": true,
"ShowThumbnailFrames": false,
"LockThumbnailLocation": false,
"ThumbnailSnapToGrid": true,
"ThumbnailSnapToGridSizeX": 107,
"ThumbnailSnapToGridSizeY": 35,
"EnableActiveClientHighlight": true,
"ActiveClientHighlightColor": "GreenYellow",
"OverlayLabelColor": "Orange",
"OverlayLabelSize": 10,
"EnableThumbnailRegionSnipping": true,
"DefaultThumbnailRegion": "1205, 1269, 429, 140",
"CurrentProfile": "Default",
"AvailableProfiles": [
"Default"
],
"IconName": "IconOriginal",
"ActiveClientHighlightThickness": 3,
"LoginThumbnailLocation": "5, 5",
"ToggleTrackingHotkey": "Alt+F16",
"ToggleSingleProcessHotkey": "Control+F16",
"ToggleAllThumbnailsHotkey": "Alt+Oem3",
"ThumbnailsManuallyHidden": false,
"PerClientLayout": {},
"FlatLayout": {
"EVE - Quartio": "0, 1295",
"EVE - Tertiale": "0, 1155",
"EVE - Secundamen": "0, 1015",
"EVE - Primorium": "0, 875",
"EVE - PhatPhuckDave": "0, 735",
"EVE - Quartomo": "0, 1295",
"EVE - Tertiashi": "0, 1155",
"EVE - Secundaru": "0, 1015",
"EVE - Primosaki": "0, 875",
"EVE - PhuckingWeebDave": "0, 735"
},
"ClientLayout": {},
"ClientHotkey": {},
"DisableThumbnail": {},
"PriorityClients": [],
"ExecutablesToPreview": [
"exefile"
]
}

View File

@@ -0,0 +1,130 @@
{
"ConfigVersion": 1,
"CycleGroup1ForwardHotkeys": [
"F14",
"Control+F14"
],
"CycleGroup1BackwardHotkeys": [
"F13",
"Control+F13"
],
"CycleGroup1ClientsOrder": {
"EVE - Example DPS Toon 1": 1,
"EVE - Example DPS Toon 2": 2,
"EVE - Example DPS Toon 3": 3
},
"CycleGroup2ForwardHotkeys": [
"F16",
"Control+F16"
],
"CycleGroup2BackwardHotkeys": [
"F15",
"Control+F15"
],
"CycleGroup2ClientsOrder": {
"EVE - Example Logi Toon 1": 1,
"EVE - Example Scout Toon 2": 2,
"EVE - Example Tackle Toon 3": 3
},
"CycleGroup3ForwardHotkeys": [
""
],
"CycleGroup3BackwardHotkeys": [
""
],
"CycleGroup3ClientsOrder": {
"EVE - cycle group 3": 1
},
"CycleGroup4ForwardHotkeys": [
""
],
"CycleGroup4BackwardHotkeys": [
""
],
"CycleGroup4ClientsOrder": {
"EVE - cycle group 4": 1
},
"CycleGroup5ForwardHotkeys": [
""
],
"CycleGroup5BackwardHotkeys": [
""
],
"CycleGroup5ClientsOrder": {
"EVE - cycle group 5": 1
},
"PerClientActiveClientHighlightColor": {
"EVE - Example Toon 1": "Red",
"EVE - Example Toon 2": "Green"
},
"PerClientThumbnailSize": {
"EVE - Example Toon 1": "200, 200",
"EVE - Example Toon 2": "200, 200"
},
"PerClientThumbnailRegion": {},
"PerClientZoomAnchor": {
"EVE - Example Toon 1": 1,
"EVE - Example Toon 2": 7
},
"MinimizeToTray": true,
"ThumbnailRefreshPeriod": 500,
"ThumbnailResizeTimeoutPeriod": 500,
"WineCompatibilityMode": false,
"ThumbnailsOpacity": 1.0,
"EnableClientLayoutTracking": false,
"HideActiveClientThumbnail": false,
"HideLoginClientThumbnail": false,
"MinimizeInactiveClients": false,
"WindowsAnimationStyle": 1,
"ShowThumbnailsAlwaysOnTop": true,
"EnablePerClientThumbnailLayouts": false,
"HideThumbnailsOnLostFocus": false,
"HideThumbnailsDelay": 2,
"ThumbnailSize": "351, 148",
"ThumbnailMaximumSize": "960, 540",
"ThumbnailMinimumSize": "192, 108",
"EnableThumbnailSnap": true,
"ThumbnailSnapRange": 0,
"EnableThumbnailZoom": false,
"ThumbnailZoomFactor": 2,
"ThumbnailZoomAnchor": 0,
"OverlayLabelAnchor": 0,
"ShowThumbnailOverlays": true,
"ShowThumbnailFrames": false,
"LockThumbnailLocation": false,
"ThumbnailSnapToGrid": true,
"ThumbnailSnapToGridSizeX": 87,
"ThumbnailSnapToGridSizeY": 37,
"EnableActiveClientHighlight": true,
"ActiveClientHighlightColor": "GreenYellow",
"OverlayLabelColor": "Orange",
"OverlayLabelSize": 10,
"EnableThumbnailRegionSnipping": true,
"DefaultThumbnailRegion": "1764, 988, 351, 148",
"CurrentProfile": "Default",
"AvailableProfiles": [
"Default"
],
"IconName": "IconOriginal",
"ActiveClientHighlightThickness": 3,
"LoginThumbnailLocation": "5, 5",
"ToggleTrackingHotkey": "Alt+F16",
"ToggleSingleProcessHotkey": "Control+F16",
"ToggleAllThumbnailsHotkey": "Alt+Oem3",
"ThumbnailsManuallyHidden": false,
"PerClientLayout": {},
"FlatLayout": {
"EVE - Quartio": "-348, 814",
"EVE - Tertiale": "-348, 666",
"EVE - Secundamen": "-348, 518",
"EVE - Primorium": "-348, 370",
"EVE - PhatPhuckDave": "-348, 222"
},
"ClientLayout": {},
"ClientHotkey": {},
"DisableThumbnail": {},
"PriorityClients": [],
"ExecutablesToPreview": [
"exefile"
]
}

View File

@@ -0,0 +1,130 @@
{
"ConfigVersion": 1,
"CycleGroup1ForwardHotkeys": [
"F14",
"Control+F14"
],
"CycleGroup1BackwardHotkeys": [
"F13",
"Control+F13"
],
"CycleGroup1ClientsOrder": {
"EVE - Example DPS Toon 1": 1,
"EVE - Example DPS Toon 2": 2,
"EVE - Example DPS Toon 3": 3
},
"CycleGroup2ForwardHotkeys": [
"F16",
"Control+F16"
],
"CycleGroup2BackwardHotkeys": [
"F15",
"Control+F15"
],
"CycleGroup2ClientsOrder": {
"EVE - Example Logi Toon 1": 1,
"EVE - Example Scout Toon 2": 2,
"EVE - Example Tackle Toon 3": 3
},
"CycleGroup3ForwardHotkeys": [
""
],
"CycleGroup3BackwardHotkeys": [
""
],
"CycleGroup3ClientsOrder": {
"EVE - cycle group 3": 1
},
"CycleGroup4ForwardHotkeys": [
""
],
"CycleGroup4BackwardHotkeys": [
""
],
"CycleGroup4ClientsOrder": {
"EVE - cycle group 4": 1
},
"CycleGroup5ForwardHotkeys": [
""
],
"CycleGroup5BackwardHotkeys": [
""
],
"CycleGroup5ClientsOrder": {
"EVE - cycle group 5": 1
},
"PerClientActiveClientHighlightColor": {
"EVE - Example Toon 1": "Red",
"EVE - Example Toon 2": "Green"
},
"PerClientThumbnailSize": {
"EVE - Example Toon 1": "200, 200",
"EVE - Example Toon 2": "200, 200"
},
"PerClientThumbnailRegion": {},
"PerClientZoomAnchor": {
"EVE - Example Toon 1": 1,
"EVE - Example Toon 2": 7
},
"MinimizeToTray": true,
"ThumbnailRefreshPeriod": 500,
"ThumbnailResizeTimeoutPeriod": 500,
"WineCompatibilityMode": false,
"ThumbnailsOpacity": 1.0,
"EnableClientLayoutTracking": false,
"HideActiveClientThumbnail": false,
"HideLoginClientThumbnail": false,
"MinimizeInactiveClients": false,
"WindowsAnimationStyle": 1,
"ShowThumbnailsAlwaysOnTop": true,
"EnablePerClientThumbnailLayouts": false,
"HideThumbnailsOnLostFocus": false,
"HideThumbnailsDelay": 2,
"ThumbnailSize": "451, 238",
"ThumbnailMaximumSize": "960, 540",
"ThumbnailMinimumSize": "192, 108",
"EnableThumbnailSnap": true,
"ThumbnailSnapRange": 0,
"EnableThumbnailZoom": false,
"ThumbnailZoomFactor": 2,
"ThumbnailZoomAnchor": 0,
"OverlayLabelAnchor": 0,
"ShowThumbnailOverlays": true,
"ShowThumbnailFrames": false,
"LockThumbnailLocation": false,
"ThumbnailSnapToGrid": true,
"ThumbnailSnapToGridSizeX": 112,
"ThumbnailSnapToGridSizeY": 59,
"EnableActiveClientHighlight": true,
"ActiveClientHighlightColor": "GreenYellow",
"OverlayLabelColor": "Orange",
"OverlayLabelSize": 10,
"EnableThumbnailRegionSnipping": true,
"DefaultThumbnailRegion": "1384, 188, 451, 238",
"CurrentProfile": "Default",
"AvailableProfiles": [
"Default"
],
"IconName": "IconOriginal",
"ActiveClientHighlightThickness": 3,
"LoginThumbnailLocation": "5, 5",
"ToggleTrackingHotkey": "Alt+F16",
"ToggleSingleProcessHotkey": "Control+F16",
"ToggleAllThumbnailsHotkey": "Alt+Oem3",
"ThumbnailsManuallyHidden": false,
"PerClientLayout": {},
"FlatLayout": {
"EVE - Quartio": "0, 1295",
"EVE - Tertiale": "0, 1147",
"EVE - Secundamen": "0, 999",
"EVE - Primorium": "0, 851",
"EVE - PhatPhuckDave": "0, 703"
},
"ClientLayout": {},
"ClientHotkey": {},
"DisableThumbnail": {},
"PriorityClients": [],
"ExecutablesToPreview": [
"exefile"
]
}

View File

@@ -0,0 +1,130 @@
{
"ConfigVersion": 1,
"CycleGroup1ForwardHotkeys": [
"F14",
"Control+F14"
],
"CycleGroup1BackwardHotkeys": [
"F13",
"Control+F13"
],
"CycleGroup1ClientsOrder": {
"EVE - Example DPS Toon 1": 1,
"EVE - Example DPS Toon 2": 2,
"EVE - Example DPS Toon 3": 3
},
"CycleGroup2ForwardHotkeys": [
"F16",
"Control+F16"
],
"CycleGroup2BackwardHotkeys": [
"F15",
"Control+F15"
],
"CycleGroup2ClientsOrder": {
"EVE - Example Logi Toon 1": 1,
"EVE - Example Scout Toon 2": 2,
"EVE - Example Tackle Toon 3": 3
},
"CycleGroup3ForwardHotkeys": [
""
],
"CycleGroup3BackwardHotkeys": [
""
],
"CycleGroup3ClientsOrder": {
"EVE - cycle group 3": 1
},
"CycleGroup4ForwardHotkeys": [
""
],
"CycleGroup4BackwardHotkeys": [
""
],
"CycleGroup4ClientsOrder": {
"EVE - cycle group 4": 1
},
"CycleGroup5ForwardHotkeys": [
""
],
"CycleGroup5BackwardHotkeys": [
""
],
"CycleGroup5ClientsOrder": {
"EVE - cycle group 5": 1
},
"PerClientActiveClientHighlightColor": {
"EVE - Example Toon 1": "Red",
"EVE - Example Toon 2": "Green"
},
"PerClientThumbnailSize": {
"EVE - Example Toon 1": "200, 200",
"EVE - Example Toon 2": "200, 200"
},
"PerClientThumbnailRegion": {},
"PerClientZoomAnchor": {
"EVE - Example Toon 1": 1,
"EVE - Example Toon 2": 7
},
"MinimizeToTray": true,
"ThumbnailRefreshPeriod": 500,
"ThumbnailResizeTimeoutPeriod": 500,
"WineCompatibilityMode": false,
"ThumbnailsOpacity": 1.0,
"EnableClientLayoutTracking": false,
"HideActiveClientThumbnail": false,
"HideLoginClientThumbnail": false,
"MinimizeInactiveClients": false,
"WindowsAnimationStyle": 1,
"ShowThumbnailsAlwaysOnTop": true,
"EnablePerClientThumbnailLayouts": false,
"HideThumbnailsOnLostFocus": false,
"HideThumbnailsDelay": 2,
"ThumbnailSize": "451, 388",
"ThumbnailMaximumSize": "960, 540",
"ThumbnailMinimumSize": "192, 108",
"EnableThumbnailSnap": true,
"ThumbnailSnapRange": 0,
"EnableThumbnailZoom": false,
"ThumbnailZoomFactor": 2,
"ThumbnailZoomAnchor": 0,
"OverlayLabelAnchor": 0,
"ShowThumbnailOverlays": true,
"ShowThumbnailFrames": false,
"LockThumbnailLocation": false,
"ThumbnailSnapToGrid": true,
"ThumbnailSnapToGridSizeX": 112,
"ThumbnailSnapToGridSizeY": 97,
"EnableActiveClientHighlight": true,
"ActiveClientHighlightColor": "GreenYellow",
"OverlayLabelColor": "Orange",
"OverlayLabelSize": 10,
"EnableThumbnailRegionSnipping": true,
"DefaultThumbnailRegion": "1384, 188, 451, 388",
"CurrentProfile": "Default",
"AvailableProfiles": [
"Default"
],
"IconName": "IconOriginal",
"ActiveClientHighlightThickness": 3,
"LoginThumbnailLocation": "5, 5",
"ToggleTrackingHotkey": "Alt+F16",
"ToggleSingleProcessHotkey": "Control+F16",
"ToggleAllThumbnailsHotkey": "Alt+Oem3",
"ThumbnailsManuallyHidden": true,
"PerClientLayout": {},
"FlatLayout": {
"EVE - Quartio": "-1120, 1067",
"EVE - Tertiale": "-1568, 1067",
"EVE - Secundamen": "-2016, 1067",
"EVE - Primorium": "-2464, 679",
"EVE - PhatPhuckDave": "-2464, 291"
},
"ClientLayout": {},
"ClientHotkey": {},
"DisableThumbnail": {},
"PriorityClients": [],
"ExecutablesToPreview": [
"exefile"
]
}

View File

@@ -0,0 +1,43 @@
{
"runtimeTarget": {
"name": ".NETCoreApp,Version=v6.0",
"signature": ""
},
"compilationOptions": {},
"targets": {
".NETCoreApp,Version=v6.0": {
"ExeFile/1.0.0": {
"dependencies": {
"Microsoft.CSharp": "4.7.0",
"System.Data.DataSetExtensions": "4.5.0"
},
"runtime": {
"ExeFile.dll": {}
}
},
"Microsoft.CSharp/4.7.0": {},
"System.Data.DataSetExtensions/4.5.0": {}
}
},
"libraries": {
"ExeFile/1.0.0": {
"type": "project",
"serviceable": false,
"sha512": ""
},
"Microsoft.CSharp/4.7.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-pTj+D3uJWyN3My70i2Hqo+OXixq3Os2D1nJ2x92FFo6sk8fYS1m1WLNTs0Dc1uPaViH0YvEEwvzddQ7y4rhXmA==",
"path": "microsoft.csharp/4.7.0",
"hashPath": "microsoft.csharp.4.7.0.nupkg.sha512"
},
"System.Data.DataSetExtensions/4.5.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-221clPs1445HkTBZPL+K9sDBdJRB8UN8rgjO3ztB0CQ26z//fmJXtlsr6whGatscsKGBrhJl5bwJuKSA8mwFOw==",
"path": "system.data.datasetextensions/4.5.0",
"hashPath": "system.data.datasetextensions.4.5.0.nupkg.sha512"
}
}
}

View File

@@ -0,0 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
</configuration>

View File

@@ -0,0 +1,18 @@
{
"runtimeOptions": {
"tfm": "net6.0",
"frameworks": [
{
"name": "Microsoft.NETCore.App",
"version": "6.0.0"
},
{
"name": "Microsoft.WindowsDesktop.App",
"version": "6.0.0"
}
],
"configProperties": {
"System.Reflection.Metadata.MetadataUpdater.IsSupported": false
}
}
}

View File

@@ -1,16 +1,32 @@
using System.IO;
using System;
using System.IO;
using Newtonsoft.Json;
namespace EveOPreview.Configuration.Implementation {
class ConfigurationStorage : IConfigurationStorage {
private const string CONFIGURATION_FILE_NAME = "EVE-O-Preview.json";
private const string PROFILES_FOLDER_NAME = "Profiles";
private const string PROFILE_CONFIG_FILE_EXTENSION = ".json";
private readonly IAppConfig _appConfig;
private readonly IThumbnailConfiguration _thumbnailConfiguration;
private readonly string _baseDirectory;
private string _currentProfile;
public ConfigurationStorage(IAppConfig appConfig, IThumbnailConfiguration thumbnailConfiguration) {
this._appConfig = appConfig;
this._thumbnailConfiguration = thumbnailConfiguration;
this._baseDirectory = AppDomain.CurrentDomain.BaseDirectory;
this._currentProfile = "Default";
}
public string CurrentProfile {
get => this._currentProfile;
set {
if (this._currentProfile != value) {
this._currentProfile = value ?? "Default";
}
}
}
public void Load() {
@@ -38,6 +54,12 @@ namespace EveOPreview.Configuration.Implementation {
string filename = this.GetConfigFileName();
try {
// Ensure the directory exists before saving
string directory = Path.GetDirectoryName(filename);
if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory)) {
Directory.CreateDirectory(directory);
}
File.WriteAllText(filename, rawData);
} catch (IOException) {
// Ignore error if for some reason the updated config cannot be written down
@@ -45,8 +67,19 @@ namespace EveOPreview.Configuration.Implementation {
}
private string GetConfigFileName() {
return string.IsNullOrEmpty(this._appConfig.ConfigFileName) ? ConfigurationStorage.CONFIGURATION_FILE_NAME
: this._appConfig.ConfigFileName;
// If a custom config file is specified via command line, use it
if (!string.IsNullOrEmpty(this._appConfig.ConfigFileName)) {
return this._appConfig.ConfigFileName;
}
// If using the Default profile or no profile is set, use the main config file
if (string.IsNullOrEmpty(this._currentProfile) || this._currentProfile == "Default") {
return Path.Combine(this._baseDirectory, CONFIGURATION_FILE_NAME);
}
// For named profiles, use the profile-specific config file in the Profiles folder
string profilesFolder = Path.Combine(this._baseDirectory, PROFILES_FOLDER_NAME);
return Path.Combine(profilesFolder, this._currentProfile + PROFILE_CONFIG_FILE_EXTENSION);
}
}
}

View File

@@ -0,0 +1,285 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Security.AccessControl;
using System.Security.Principal;
using System.Threading;
using System.Windows.Forms;
using Newtonsoft.Json;
namespace EveOPreview.Configuration.Implementation {
class ProfileManager : IProfileManager {
private const string PROFILES_FOLDER_NAME = "Profiles";
private const string MASTER_CONFIG_FILE_NAME = "EVE-O-Preview-Master.json";
private const string PROFILE_CONFIG_FILE_EXTENSION = ".json";
private const string PROFILE_LOCK_MUTEX_PREFIX = "EVE-O-Preview_ProfileLock_";
private readonly IAppConfig _appConfig;
private readonly IConfigurationStorage _configurationStorage;
private readonly string _baseDirectory;
private readonly Dictionary<string, Mutex> _acquiredLocks;
private Mutex _currentProfileLock;
public ProfileManager(IAppConfig appConfig, IConfigurationStorage configurationStorage) {
this._appConfig = appConfig;
this._configurationStorage = configurationStorage;
// Get the base directory (where the main config file is located)
// We'll use the application base directory
this._baseDirectory = AppDomain.CurrentDomain.BaseDirectory;
// Ensure Profiles folder exists
this.EnsureProfilesFolderExists();
// Ensure master config exists
this.EnsureMasterConfigExists();
this._acquiredLocks = new Dictionary<string, Mutex>();
this._currentProfileLock = null;
}
public System.Collections.Generic.List<string> GetAvailableProfiles() {
string profilesPath = this.GetProfilesFolderPath();
var profiles = new System.Collections.Generic.List<string> { "Default" };
if (Directory.Exists(profilesPath)) {
var profileFiles = Directory.GetFiles(profilesPath, "*" + PROFILE_CONFIG_FILE_EXTENSION);
foreach (var file in profileFiles) {
string profileName = Path.GetFileNameWithoutExtension(file);
if (!string.IsNullOrEmpty(profileName) && !profiles.Contains(profileName)) {
profiles.Add(profileName);
}
}
}
return profiles;
}
public string GetActiveProfile() {
string masterConfigPath = this.GetMasterConfigPath();
if (!File.Exists(masterConfigPath)) {
return "Default";
}
try {
string rawData = File.ReadAllText(masterConfigPath);
var masterConfig = JsonConvert.DeserializeObject<MasterConfig>(rawData);
return masterConfig?.ActiveProfile ?? "Default";
} catch {
return "Default";
}
}
public void SwitchProfile(string profileName) {
if (string.IsNullOrEmpty(profileName)) {
profileName = "Default";
}
// Check if the profile is locked by another instance
if (this.IsProfileLocked(profileName)) {
MessageBox.Show(
$"Cannot switch to profile '{profileName}' because it is already in use by another instance.",
"Profile In Use",
MessageBoxButtons.OK,
MessageBoxIcon.Warning
);
return;
}
// Get the current instance ID
int instanceId = global::EveOPreview.Program.InstanceId;
// Get the current profile for this instance
var profiles = this.GetProfilesList();
string currentProfile = instanceId >= 0 && instanceId < profiles.Count
? profiles[instanceId]
: "Default";
// Save current configuration first
this._configurationStorage.Save();
// Release current profile lock
this.ReleaseProfileLock(currentProfile);
// Acquire new profile lock
this.AcquireProfileLock(profileName);
// Update the Profiles array for this instance
this.UpdateProfileForInstance(instanceId, profileName);
// Restart the application
this.RestartApplication();
}
public string GetProfileConfigPath(string profileName) {
if (string.IsNullOrEmpty(profileName) || profileName == "Default") {
return null;
}
return Path.Combine(this.GetProfilesFolderPath(), profileName + PROFILE_CONFIG_FILE_EXTENSION);
}
private string GetProfilesFolderPath() {
return Path.Combine(this._baseDirectory, PROFILES_FOLDER_NAME);
}
private string GetMasterConfigPath() {
return Path.Combine(this._baseDirectory, MASTER_CONFIG_FILE_NAME);
}
private void EnsureProfilesFolderExists() {
string profilesPath = this.GetProfilesFolderPath();
if (!Directory.Exists(profilesPath)) {
Directory.CreateDirectory(profilesPath);
}
}
private void EnsureMasterConfigExists() {
string masterConfigPath = this.GetMasterConfigPath();
if (!File.Exists(masterConfigPath)) {
var availableProfiles = this.GetAvailableProfiles();
var defaultConfig = new MasterConfig {
ActiveProfile = "Default",
Profiles = availableProfiles
};
string rawData = JsonConvert.SerializeObject(defaultConfig, Formatting.Indented);
File.WriteAllText(masterConfigPath, rawData);
}
}
private void SaveActiveProfile(string profileName) {
// Read existing config to preserve Profiles array
var existingConfig = this.LoadMasterConfig();
if (existingConfig == null) {
existingConfig = new MasterConfig { Profiles = this.GetAvailableProfiles() };
}
existingConfig.ActiveProfile = profileName;
string rawData = JsonConvert.SerializeObject(existingConfig, Formatting.Indented);
string masterConfigPath = this.GetMasterConfigPath();
try {
File.WriteAllText(masterConfigPath, rawData);
} catch (IOException) {
// Ignore error if config cannot be written
}
}
private MasterConfig LoadMasterConfig() {
string masterConfigPath = this.GetMasterConfigPath();
if (!File.Exists(masterConfigPath)) {
return null;
}
try {
string rawData = File.ReadAllText(masterConfigPath);
return JsonConvert.DeserializeObject<MasterConfig>(rawData);
} catch {
return null;
}
}
private void RestartApplication() {
// Call the static method in Program which properly handles the mutex
Program.RestartApplication();
}
public List<string> GetProfilesList() {
var masterConfig = this.LoadMasterConfig();
if (masterConfig?.Profiles != null && masterConfig.Profiles.Count > 0) {
return masterConfig.Profiles;
}
return this.GetAvailableProfiles();
}
public string GetProfileForInstance(int instanceId) {
var profiles = this.GetProfilesList();
if (instanceId >= 0 && instanceId < profiles.Count) {
return profiles[instanceId];
}
return "Default";
}
public bool IsProfileLocked(string profileName) {
if (string.IsNullOrEmpty(profileName) || profileName == "Default") {
return false;
}
string mutexName = PROFILE_LOCK_MUTEX_PREFIX + profileName;
try {
Mutex.OpenExisting(mutexName);
return true;
} catch (UnauthorizedAccessException) {
return true;
} catch {
return false;
}
}
public bool AcquireProfileLock(string profileName) {
if (string.IsNullOrEmpty(profileName) || profileName == "Default") {
return true;
}
string mutexName = PROFILE_LOCK_MUTEX_PREFIX + profileName;
try {
var mutex = new Mutex(true, mutexName);
this._currentProfileLock = mutex;
return true;
} catch {
return false;
}
}
public void ReleaseProfileLock(string profileName) {
if (this._currentProfileLock != null) {
this._currentProfileLock.ReleaseMutex();
this._currentProfileLock.Dispose();
this._currentProfileLock = null;
}
}
private void UpdateProfileForInstance(int instanceId, string profileName) {
var masterConfig = this.LoadMasterConfig();
if (masterConfig == null) {
masterConfig = new MasterConfig { Profiles = this.GetAvailableProfiles() };
}
// Ensure the Profiles array is large enough
if (masterConfig.Profiles == null) {
masterConfig.Profiles = this.GetAvailableProfiles();
}
// Expand the array if necessary
while (masterConfig.Profiles.Count <= instanceId) {
masterConfig.Profiles.Add("Default");
}
// Update the profile for this instance
masterConfig.Profiles[instanceId] = profileName;
// Also update ActiveProfile for backward compatibility
masterConfig.ActiveProfile = profileName;
// Save the updated config
string rawData = JsonConvert.SerializeObject(masterConfig, Formatting.Indented);
string masterConfigPath = this.GetMasterConfigPath();
try {
File.WriteAllText(masterConfigPath, rawData);
} catch (IOException) {
// Ignore error if config cannot be written
}
}
private class MasterConfig {
public string ActiveProfile { get; set; }
public List<string> Profiles { get; set; }
}
}
}

View File

@@ -0,0 +1,50 @@
using System.Collections.Generic;
namespace EveOPreview.Configuration {
public interface IProfileManager {
/// <summary>
/// Gets the list of available profile names from the Profiles folder
/// </summary>
List<string> GetAvailableProfiles();
/// <summary>
/// Gets the currently active profile name from the master config
/// </summary>
string GetActiveProfile();
/// <summary>
/// Switches to the specified profile, saves current config, updates master config, and restarts the application
/// </summary>
void SwitchProfile(string profileName);
/// <summary>
/// Gets the full path to a profile's config file
/// </summary>
string GetProfileConfigPath(string profileName);
/// <summary>
/// Gets the list of profiles from the master config array
/// </summary>
List<string> GetProfilesList();
/// <summary>
/// Gets the profile name for the specified instance ID
/// </summary>
string GetProfileForInstance(int instanceId);
/// <summary>
/// Checks if a profile is currently locked by another instance
/// </summary>
bool IsProfileLocked(string profileName);
/// <summary>
/// Acquires a lock on the specified profile for the current instance
/// </summary>
bool AcquireProfileLock(string profileName);
/// <summary>
/// Releases the lock on the specified profile
/// </summary>
void ReleaseProfileLock(string profileName);
}
}

View File

@@ -18,6 +18,7 @@ namespace EveOPreview.Presenters {
private readonly IMediator _mediator;
private readonly IThumbnailConfiguration _configuration;
private readonly IConfigurationStorage _configurationStorage;
private readonly IProfileManager _profileManager;
private readonly IDictionary<string, IThumbnailDescription> _descriptionsCache;
private bool _suppressSizeNotifications;
@@ -25,11 +26,13 @@ namespace EveOPreview.Presenters {
#endregion
public MainFormPresenter(IApplicationController controller, IMainFormView view, IMediator mediator,
IThumbnailConfiguration configuration, IConfigurationStorage configurationStorage)
IThumbnailConfiguration configuration, IConfigurationStorage configurationStorage,
IProfileManager profileManager)
: base(controller, view) {
this._mediator = mediator;
this._configuration = configuration;
this._configurationStorage = configurationStorage;
this._profileManager = profileManager;
this._descriptionsCache = new Dictionary<string, IThumbnailDescription>();
@@ -44,6 +47,7 @@ namespace EveOPreview.Presenters {
this.View.ThumbnailStateChanged = this.UpdateThumbnailState;
this.View.DocumentationLinkActivated = this.OpenDocumentationLink;
this.View.ApplicationExitRequested = this.ExitApplication;
this.View.ProfileSwitchRequested = this.SwitchProfile;
this.View.IconName = this._configuration.IconName;
}
@@ -57,10 +61,23 @@ namespace EveOPreview.Presenters {
this.View.Minimize();
}
// Initialize the tray profile menu
this.UpdateTrayProfileMenu();
this._mediator.Send(new StartService());
this._suppressSizeNotifications = false;
}
private void UpdateTrayProfileMenu() {
var profiles = this._profileManager.GetAvailableProfiles();
var currentProfile = this._profileManager.GetActiveProfile();
this.View.UpdateTrayProfileMenu(profiles, currentProfile);
}
private void SwitchProfile(string profileName) {
this._profileManager.SwitchProfile(profileName);
}
private void Minimize() {
if (!this._configuration.MinimizeToTray) {
return;

View File

@@ -1,7 +1,9 @@
using System;
using System.Linq;
using System.Threading;
using System.Windows.Forms;
using EveOPreview.Configuration;
using EveOPreview.Configuration.Implementation;
using EveOPreview.Presenters;
using EveOPreview.Services;
using EveOPreview.View;
@@ -9,17 +11,27 @@ using MediatR;
namespace EveOPreview {
static class Program {
private static string MUTEX_NAME = "EVE-O-Preview Single Instance Mutex";
private const string MUTEX_PREFIX = "EVE-O-Preview_Instance_";
private const string INSTANCE_ID_ARG = "--instance-id=";
private static Mutex _singleInstanceMutex;
private static int _instanceId = 0;
/// <summary>The main entry point for the application.</summary>
[STAThread]
static void Main() {
static void Main(string[] args) {
// Parse instance ID from command line arguments, or auto-detect next available
Program._instanceId = ParseInstanceId(args);
// If no instance ID was provided, find the next available one
if (Program._instanceId == 0 && !HasInstanceIdArgument(args)) {
Program._instanceId = FindNextAvailableInstanceId();
}
// The very usual Mutex-based single-instance screening
// 'token' variable is used to store reference to the instance Mutex
// during the app lifetime
Program._singleInstanceMutex = Program.GetInstanceToken();
Program._singleInstanceMutex = Program.GetInstanceToken(Program._instanceId);
// If it was not possible to acquire the app token then another app instance is already running
// Nothing to do here
@@ -36,20 +48,57 @@ namespace EveOPreview {
controller.Run<MainFormPresenter>();
}
private static Mutex GetInstanceToken() {
private static bool HasInstanceIdArgument(string[] args) {
if (args == null || args.Length == 0) {
return false;
}
return args.Any(a => a.StartsWith(INSTANCE_ID_ARG));
}
private static int ParseInstanceId(string[] args) {
if (args == null || args.Length == 0) {
return 0;
}
var arg = args.FirstOrDefault(a => a.StartsWith(INSTANCE_ID_ARG));
if (arg != null && int.TryParse(arg.Substring(INSTANCE_ID_ARG.Length), out int id)) {
return id;
}
return 0;
}
private static int FindNextAvailableInstanceId() {
// Try instance IDs starting from 0, find the first one that's not in use
for (int id = 0; id < 10; id++) {
string mutexName = MUTEX_PREFIX + id;
try {
Mutex.OpenExisting(mutexName);
// This instance ID is taken, try next
} catch {
// This instance ID is available
return id;
}
}
return 0; // Fallback to 0 if all are taken
}
private static Mutex GetInstanceToken(int instanceId) {
string mutexName = MUTEX_PREFIX + instanceId;
// The code might look overcomplicated here for a single Mutex operation
// Yet we had already experienced a Windows-level issue
// where .NET finalizer thread was literally paralyzed by
// a failed Mutex operation. That did lead to weird OutOfMemory
// exceptions later
try {
Mutex.OpenExisting(Program.MUTEX_NAME);
Mutex.OpenExisting(mutexName);
// if that didn't fail then another instance is already running
return null;
} catch (UnauthorizedAccessException) {
return null;
} catch (Exception) {
Mutex token = new Mutex(true, Program.MUTEX_NAME, out var result);
Mutex token = new Mutex(true, mutexName, out var result);
return result ? token : null;
}
}
@@ -78,9 +127,13 @@ namespace EveOPreview {
container.Register(typeof(IRequestHandler<, >), typeof(Program).Assembly);
// Configuration services
container.Register<IConfigurationStorage>();
container.Register<IAppConfig>();
container.Register<IThumbnailConfiguration>();
container.Register<IConfigurationStorage>();
container.Register<IProfileManager>();
// Initialize profile system
Program.InitializeProfileSystem(container);
// Application services
container.Register<IThumbnailManager>();
@@ -98,5 +151,58 @@ namespace EveOPreview {
return controller;
}
private static void InitializeProfileSystem(IIocContainer container) {
var profileManager = container.Resolve<IProfileManager>();
var configurationStorage = container.Resolve<IConfigurationStorage>();
var thumbnailConfiguration = container.Resolve<IThumbnailConfiguration>();
// Get the active profile for this instance based on instance ID
string activeProfile = profileManager.GetProfileForInstance(Program._instanceId);
// Acquire the profile lock for this instance
profileManager.AcquireProfileLock(activeProfile);
// Set the current profile in the configuration storage
if (configurationStorage is ConfigurationStorage configStorage) {
configStorage.CurrentProfile = activeProfile;
}
// Update the thumbnail configuration with the active profile
thumbnailConfiguration.CurrentProfile = activeProfile;
thumbnailConfiguration.AvailableProfiles = profileManager.GetAvailableProfiles();
}
public static void RestartApplication() {
// Get the current executable path
string executablePath = System.Diagnostics.Process.GetCurrentProcess().MainModule?.FileName;
if (string.IsNullOrEmpty(executablePath)) {
return;
}
// Release the mutex first so the new instance can start
Program._singleInstanceMutex?.Dispose();
Program._singleInstanceMutex = null;
// Small delay to ensure the mutex is fully released
System.Threading.Thread.Sleep(100);
// Start a new instance of the application with the same instance ID
try {
System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo {
FileName = executablePath,
Arguments = INSTANCE_ID_ARG + Program._instanceId,
UseShellExecute = true
});
} catch (Exception) {
// If starting the new process fails, we're in a bad state
// Just exit anyway
}
// Exit the current application immediately
Environment.Exit(0);
}
public static int InstanceId => Program._instanceId;
}
}

View File

@@ -4,6 +4,7 @@ using System.ComponentModel;
using System.Drawing;
using System.Linq;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Windows.Threading;
@@ -54,6 +55,11 @@ namespace EveOPreview.Services {
private HotkeyHandler _toggleTrackingHotkey;
private HotkeyHandler _toggleSingleProcessHotkey;
private HotkeyHandler _toggleAllThumbnailsHotkey;
private EventWaitHandle _toggleEvent;
private Thread _toggleEventThread;
private SynchronizationContext _syncContext;
private const string TOGGLE_EVENT_NAME = "EVE-O-Preview_ToggleEvent";
private bool _isToggleInitiator;
#endregion
public ThumbnailManager(IMediator mediator, IThumbnailConfiguration configuration,
@@ -76,6 +82,9 @@ namespace EveOPreview.Services {
this._thumbnailViews = new Dictionary<IntPtr, IThumbnailView>();
// Capture the current synchronization context (UI thread)
this._syncContext = SynchronizationContext.Current;
// DispatcherTimer setup
this._thumbnailUpdateTimer = new DispatcherTimer();
this._thumbnailUpdateTimer.Tick += ThumbnailUpdateTimerTick;
@@ -176,15 +185,34 @@ namespace EveOPreview.Services {
var toggleAllThumbnailsKey = this._configuration.StringToKey(this._configuration.ToggleAllThumbnailsHotkey);
this._toggleAllThumbnailsHotkey = new HotkeyHandler(mainHandle, toggleAllThumbnailsKey);
this._toggleAllThumbnailsHotkey.Pressed += (object s, HandledEventArgs e) => {
// Mark this instance as the initiator so we don't process our own event
this._isToggleInitiator = true;
this._configuration.ThumbnailsManuallyHidden = !this._configuration.ThumbnailsManuallyHidden;
this._hideThumbnailsDelay = 0;
this.RefreshThumbnails();
// Signal all other instances to toggle as well
this._toggleEvent?.Set();
// Reset the event after a short delay so other instances can process it
// Then clear the initiator flag
System.Threading.Timer resetTimer = null;
resetTimer = new System.Threading.Timer(_ => {
this._toggleEvent?.Reset();
this._isToggleInitiator = false;
resetTimer?.Dispose();
}, null, 50, System.Threading.Timeout.Infinite);
System.Diagnostics.Debug.WriteLine(
$"Toggled all thumbnails: {(this._configuration.ThumbnailsManuallyHidden ? "Hidden" : "Visible")}");
e.Handled = true;
};
registered = this._toggleAllThumbnailsHotkey.Register();
System.Diagnostics.Debug.WriteLine($"Toggle all thumbnails hotkey registration result: {registered}");
// Initialize the toggle event for multi-instance synchronization
this.InitializeToggleEvent();
}
public void ReRegisterHotkeys() {
@@ -257,9 +285,25 @@ namespace EveOPreview.Services {
var toggleAllThumbnailsKey = this._configuration.StringToKey(this._configuration.ToggleAllThumbnailsHotkey);
this._toggleAllThumbnailsHotkey = new HotkeyHandler(mainHandle, toggleAllThumbnailsKey);
this._toggleAllThumbnailsHotkey.Pressed += (object s, HandledEventArgs e) => {
// Mark this instance as the initiator so we don't process our own event
this._isToggleInitiator = true;
this._configuration.ThumbnailsManuallyHidden = !this._configuration.ThumbnailsManuallyHidden;
this._hideThumbnailsDelay = 0;
this.RefreshThumbnails();
// Signal all other instances to toggle as well
this._toggleEvent?.Set();
// Reset the event after a short delay so other instances can process it
// Then clear the initiator flag
System.Threading.Timer resetTimer = null;
resetTimer = new System.Threading.Timer(_ => {
this._toggleEvent?.Reset();
this._isToggleInitiator = false;
resetTimer?.Dispose();
}, null, 50, System.Threading.Timeout.Infinite);
System.Diagnostics.Debug.WriteLine(
$"Toggled all thumbnails: {(this._configuration.ThumbnailsManuallyHidden ? "Hidden" : "Visible")}");
e.Handled = true;
@@ -267,6 +311,62 @@ namespace EveOPreview.Services {
this._toggleAllThumbnailsHotkey.Register();
}
private void InitializeToggleEvent() {
bool created;
try {
// Create or open the named event - use ManualReset to wake all instances
this._toggleEvent = new EventWaitHandle(false, EventResetMode.ManualReset, TOGGLE_EVENT_NAME, out created);
if (!created) {
// Event already exists, use it
this._toggleEvent = EventWaitHandle.OpenExisting(TOGGLE_EVENT_NAME);
}
} catch {
// Fallback: create a new one
this._toggleEvent = new EventWaitHandle(false, EventResetMode.ManualReset, TOGGLE_EVENT_NAME);
}
// Start a thread to listen for toggle events from other instances
this._toggleEventThread = new Thread(this.ToggleEventListener) {
IsBackground = true,
Name = "ToggleEventListener"
};
this._toggleEventThread.Start();
}
private void ToggleEventListener() {
while (true) {
try {
// Wait for the toggle event
this._toggleEvent?.WaitOne();
// Reset the event so we can wait again
this._toggleEvent?.Reset();
// Skip processing if we initiated this toggle
// Check inside the UI thread callback to avoid race conditions
this._syncContext?.Post(_ => {
// Double-check the flag on the UI thread after a small delay
// to ensure the initiator's flag is still set
System.Threading.Thread.Sleep(10);
if (this._isToggleInitiator) {
return; // Skip - we initiated this toggle
}
this._configuration.ThumbnailsManuallyHidden = !this._configuration.ThumbnailsManuallyHidden;
this._hideThumbnailsDelay = 0;
this.RefreshThumbnails();
System.Diagnostics.Debug.WriteLine(
$"Received toggle event: {(this._configuration.ThumbnailsManuallyHidden ? "Hidden" : "Visible")}");
}, null);
} catch (ThreadAbortException) {
// Thread is being aborted, exit gracefully
break;
} catch {
// Ignore other errors and continue listening
}
}
}
public IThumbnailView GetClientByTitle(string title) {
return _thumbnailViews.FirstOrDefault(x => x.Value.Title == title).Value;
}

View File

@@ -45,6 +45,9 @@ namespace EveOPreview.View {
// Add mouse wheel event handlers for aspect ratio maintenance
this.ThumbnailsWidthNumericEdit.MouseWheel += ThumbnailSizeNumeric_MouseWheel;
this.ThumbnailsHeightNumericEdit.MouseWheel += ThumbnailSizeNumeric_MouseWheel;
// Set tray icon text with instance ID
this.SetTrayIconText();
}
public bool MinimizeToTray {
@@ -419,6 +422,71 @@ namespace EveOPreview.View {
public Action DocumentationLinkActivated { get; set; }
public Action<string> ProfileSwitchRequested { get; set; }
public void UpdateTrayProfileMenu(List<string> profiles, string currentProfile) {
// Clear existing items from the tray menu
this.TrayMenu.Items.Clear();
// Add title
ToolStripMenuItem titleMenuItem = new ToolStripMenuItem();
titleMenuItem.Enabled = false;
titleMenuItem.Name = "TitleMenuItem";
titleMenuItem.Size = new System.Drawing.Size(200, 22);
titleMenuItem.Text = "EVE-O-Preview";
this.TrayMenu.Items.Add(titleMenuItem);
// Add restore option
ToolStripMenuItem restoreMenuItem = new ToolStripMenuItem();
restoreMenuItem.Name = "RestoreWindowMenuItem";
restoreMenuItem.Size = new System.Drawing.Size(200, 22);
restoreMenuItem.Text = "Restore";
restoreMenuItem.Click += this.RestoreMainForm_Handler;
this.TrayMenu.Items.Add(restoreMenuItem);
// Add separator
ToolStripSeparator separatorMenuItem = new ToolStripSeparator();
separatorMenuItem.Name = "SeparatorMenuItem";
separatorMenuItem.Size = new System.Drawing.Size(200, 6);
this.TrayMenu.Items.Add(separatorMenuItem);
// Add profile submenu
ToolStripMenuItem profileMenuItem = new ToolStripMenuItem();
profileMenuItem.Name = "ProfileMenuItem";
profileMenuItem.Size = new System.Drawing.Size(200, 22);
profileMenuItem.Text = "Profiles";
foreach (var profile in profiles) {
ToolStripMenuItem profileItem = new ToolStripMenuItem();
profileItem.Name = "Profile_" + profile;
profileItem.Size = new System.Drawing.Size(180, 22);
profileItem.Text = profile;
profileItem.Checked = (profile == currentProfile);
profileItem.Click += (sender, e) => {
this.ProfileSwitchRequested?.Invoke(profile);
};
profileMenuItem.DropDownItems.Add(profileItem);
}
this.TrayMenu.Items.Add(profileMenuItem);
// Add final separator
ToolStripSeparator finalSeparatorMenuItem = new ToolStripSeparator();
finalSeparatorMenuItem.Name = "FinalSeparatorMenuItem";
finalSeparatorMenuItem.Size = new System.Drawing.Size(200, 6);
this.TrayMenu.Items.Add(finalSeparatorMenuItem);
// Add exit option
ToolStripMenuItem exitMenuItem = new ToolStripMenuItem();
exitMenuItem.Name = "ExitMenuItem";
exitMenuItem.Size = new System.Drawing.Size(200, 22);
exitMenuItem.Text = "Exit";
exitMenuItem.Click += this.ExitMenuItemClick_Handler;
this.TrayMenu.Items.Add(exitMenuItem);
}
#region UI events
private void ContentTabControl_DrawItem(object sender, DrawItemEventArgs e) {
TabControl control = (TabControl)sender;
@@ -670,6 +738,12 @@ namespace EveOPreview.View {
}
}
private void SetTrayIconText() {
// Get the instance ID from Program
int instanceId = EveOPreview.Program.InstanceId;
this.NotifyIcon.Text = $"EVE-O-Preview [{instanceId}]";
}
private void AnimationStyleCombo_SelectedIndexChanged(object sender, EventArgs e) {}
private void GeneralSettingsPanel_Paint(object sender, PaintEventArgs e) {}

View File

@@ -63,6 +63,9 @@ namespace EveOPreview.View {
void RemoveThumbnails(IList<IThumbnailDescription> thumbnails);
void RefreshZoomSettings();
void UpdateTrayProfileMenu(List<string> profiles, string currentProfile);
Action<string> ProfileSwitchRequested { get; set; }
Action ApplicationExitRequested { get; set; }
Action FormActivated { get; set; }
Action FormMinimized { get; set; }

View File

@@ -0,0 +1,91 @@
{
"runtimeTarget": {
"name": ".NETCoreApp,Version=v8.0",
"signature": ""
},
"compilationOptions": {},
"targets": {
".NETCoreApp,Version=v8.0": {
"EVE-O-Preview/1.0.0": {
"dependencies": {
"LightInject": "7.0.1",
"MediatR": "9.0.0",
"Microsoft.CSharp": "4.7.0",
"Microsoft.NET.ILLink.Tasks": "8.0.8",
"Newtonsoft.Json": "13.0.3"
},
"runtime": {
"EVE-O-Preview.dll": {}
}
},
"LightInject/7.0.1": {
"runtime": {
"lib/net8.0/LightInject.dll": {
"assemblyVersion": "7.0.1.0",
"fileVersion": "7.0.1.0"
}
}
},
"MediatR/9.0.0": {
"runtime": {
"lib/netstandard2.1/MediatR.dll": {
"assemblyVersion": "9.0.0.0",
"fileVersion": "9.0.0.0"
}
}
},
"Microsoft.CSharp/4.7.0": {},
"Microsoft.NET.ILLink.Tasks/8.0.8": {},
"Newtonsoft.Json/13.0.3": {
"runtime": {
"lib/net6.0/Newtonsoft.Json.dll": {
"assemblyVersion": "13.0.0.0",
"fileVersion": "13.0.3.27908"
}
}
}
}
},
"libraries": {
"EVE-O-Preview/1.0.0": {
"type": "project",
"serviceable": false,
"sha512": ""
},
"LightInject/7.0.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-aw4ayG2Pe68i+85ws8zYk7MPCKjEd4DeBoTqVvmjA2cfpYYNnw+v0E5T3PKdriKaxdKF+eUzlnxWWZnYK/gx4w==",
"path": "lightinject/7.0.1",
"hashPath": "lightinject.7.0.1.nupkg.sha512"
},
"MediatR/9.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-8b3UYNxegHVYcJMG2zH8wn+YqxLvXG+eMfj0cMCq/jTW72p6O3PCKMkrIv0mqyxdW7bA4gblsocw7n+/9Akg5g==",
"path": "mediatr/9.0.0",
"hashPath": "mediatr.9.0.0.nupkg.sha512"
},
"Microsoft.CSharp/4.7.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-pTj+D3uJWyN3My70i2Hqo+OXixq3Os2D1nJ2x92FFo6sk8fYS1m1WLNTs0Dc1uPaViH0YvEEwvzddQ7y4rhXmA==",
"path": "microsoft.csharp/4.7.0",
"hashPath": "microsoft.csharp.4.7.0.nupkg.sha512"
},
"Microsoft.NET.ILLink.Tasks/8.0.8": {
"type": "package",
"serviceable": true,
"sha512": "sha512-P8wR6MUWwYXIjPJuBaZgo5zlI/GWI6QEAo6NyVIbPefa9CCkohYu7dP2rD/mrqnjEqfRHyl+h9VZrDoGpELqYg==",
"path": "microsoft.net.illink.tasks/8.0.8",
"hashPath": "microsoft.net.illink.tasks.8.0.8.nupkg.sha512"
},
"Newtonsoft.Json/13.0.3": {
"type": "package",
"serviceable": true,
"sha512": "sha512-HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ==",
"path": "newtonsoft.json/13.0.3",
"hashPath": "newtonsoft.json.13.0.3.nupkg.sha512"
}
}
}

View File

@@ -0,0 +1,18 @@
{
"runtimeOptions": {
"tfm": "net8.0",
"frameworks": [
{
"name": "Microsoft.NETCore.App",
"version": "8.0.0"
},
{
"name": "Microsoft.WindowsDesktop.App",
"version": "8.0.0"
}
],
"configProperties": {
"System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization": true
}
}
}