What are good array alternatives other than Unraid?

As the title implies I am thinking of building a NAS to both safeguard and consolidate my various drives. I'm going to go with something like a terramaster or anything prebuilt that can take at least 6 drives.

My concern was with the array configuration I should use. Right now I have only 4 12TB drives, my intention is to set them up with one parity drive, then later once I properly send or store the data I have on the other 14TB and 18TB hard drives I would add them to the array and hopefully have 2 parity drives.

Since my drives are all different sizes, and I don't want to risk losing any data if something happens to a data drive or a parity I wanted to avoid stuff like RAID 5. Enter Unraid, which seems like a good choice, let's me use different sized drives, can have parity and while I may need to ensure my NAS has an x86 processor and that I'll lose speed since the data is not stripped over multiple drives, I'd rather live with 240MB/s speeds than risk anything ( I also don't have any files that require more than that speed)

But before I set my eyes on anything, I want to check here if there are other array options aside from RAID and Unraid. Those are the only two I'm aware of.

reddit.com
u/Blackirean — 14 days ago
🔥 Hot ▲ 11.3k r/TopCharacterTropes

Everybody's packing heat

Hot Fuzz: Every member of the city council is packing heat. Even the Priest

Dirty Pair Flash: Everyone packs heat. One guy's girlfriend turned out to be a robot and she's pack to the brim with firepower

u/Blackirean — 1 month ago
🔥 Hot ▲ 7.2k r/TopCharacterTropes

Weaknesses/Effects not present in the source material but make total sense when included in the story

MCU and The power of the stones being harmful: in the films it was established that the infinity stones immense power is hard or impossible to handle for mortal beings, with only celestials and other powerful entities able to withstand it thank to special tools like the infinity gauntlet. But even then, when together their energy is shown to overwhelm any who wields them; severely burning them and even killing them if they're not strong enough, regardless of tools. Which makes total sense, as they are physical representations of aspects of the universe itself and are supposed to grant infinite power when united, which would naturally destroy nearly everyone who tries to use them.

Invincible and Viltrumite durability: In the show during the viltrumite war, a serum made from some beetles had the ability to make viltrumites less durable, seen when Allen manged to kill one by punching right trough him. In the comics however Allen did this by himself simply using his new strength, commenting that some viltrumites were more resilient than others. The show is a much better representation, as they are supposed to be the most powerful species in the universe. And makes more sense than both Allen suddenly becoming stronger than most viltrumites after almost dying, and the supposedly strongest species having such weak members amongst the 50 that survived the virus that almost wiped them out. Their strength is supposed to be almost unobtainable, and the show made a good job showing that to beat them you either had to be in possession of the strongest weapon, be a full or half viltrumite, be a genetically designed anomaly or straight up cheat and use some random thing that affects their specific biology.

X-men and Magneto's helmet: The movies introduce an interesting and so in-universe perfect concept that it is a surprise it wasn't part of the original lore, that is the fact that Magneto's helmet serves as protection from telepathy. The helmet has been a part of magneto's design for decades and considering his history with Charles Xavier and other Mutant telepaths like Emma Frost or Jean Grey, it would be totally in character for him to obtain a method to protect himself against mental attacks. After all he is one of the most powerful mutants ever, to the point he's almost unstoppable by most physical means and telepathy is one of the few powers that pose an actual threat to him.

u/Blackirean — 1 month ago

How can I make a Macro that detects no Wi-Fi connection then turns on mobile data without needing location service?

I'm a bit picky about having either of those three on at once. So I created 2 macros and some custom widgets to trigger them, one is a simple button to turn location on/off, and I only use it when using maps, otherwise the setting is always off. the other turns my data on and switches wifi off and vice versa or switches data off if both are activated at the same time. I'm normally good at triggering the thing manually when I leave home for work or something but in more than one occasion I've missed important calls and texts because I left in a hurry and forgot to launch the macro to enable data and turn off wifi.

My idea was to make a new one, that would detect that if I'm disconnected from any wifi network for over 20 minutes, wifi is on and mobile data is off then it would switch it on and turn off wifi, or simply launch my other macro that I have for that.

However, apparently you can't detect wifi status or network connection without having location on, even if it's to determine if ANY wifi network is connected. Is there any way to circumvent this?

reddit.com
u/Blackirean — 1 month ago

Old forgotten tropes that barely survive only because of their influence on other media

Clown TV: In the most prominent example of this, Krusty the Clown is the remnant of a now mostly extinct era of 70s and 80s TV variety shows. These were often hosted by entertainers or in this case clowns, that were filmed in front of live audiences and consisted mostly of sketches and cartoon shorts with the occasional guest and sometimes animal performers. Those kinds of shows have mostly disappeared and yet Krusty is one of the few things that remain thanks to their influence and the fact he premiered close to the era they were prominent.

Jughead's Whoopee Cap: While Archie comics are not as prominent as they once were, they are still going on as of today. And one particularly interesting aspect is the hat that is worn by Jughead. Nicknamed the whoopee cap, this kind of hat was once popular in the 40s and 50s, and comes from inverting a fedora and trimming the edges, then decorating it with buttons and other accessories. These hats came from a time where nearly everyone under the sun wore fedora hats to all places, and was primarily associated with either children (as those who would most likely mess with their hats) or childish/ immature characters. Fedoras mostly fell out of fashion and the whoopee hat died with them, but the prevalence of Archie comics has managed to keep it from disappearing completely from the public eye, even though most people think of it as a simple crown styled hat, and more of a stylistic choice.

Tea Party: A rather common example is in media often showing kids, especially little girls having tea parties with their dolls. This stemmed from kids decades ago watching their mothers have tea parties with their friends, and then imitating them using toys. It was rather common for women to gather with their friends or neighbors for tea, and thus for kids to imitate them. This became such a common occurrence that it slipped into the early days of television, and then it was carried over by shows copying or referencing other shows, specifically with the trope of what little girl characters do with their toys. Tea parties are nowhere near as common as they once were, but it's funny that the main reason it's on people's minds is because at some point we've definitely seen a kid on TV doing them.

u/Blackirean — 1 month ago
▲ 5 r/NR200

Has anybody installed a wireless power switch in their NR200?

I'm rocking an original NR200 V1, and I was thinking of buying a remote control to turn the PC on and off. However I have two main issues.

One is that in my current configuration I don't know if I have the proper space. I have a Noctua NH-U12A with an Aorus B850i Motherboard, V850SFX and custom sleeved cables. There is barely enough space as it is, between the motherboard and PSU, that is where I route my cables. Left of that is the RAM and then my cooler. I'm not entirely sure where the remote module could fit

Second is the module itself. The most prominent example I've found is a Silverstone remote SST-ES02-USB. Is that the only option or does anyone know of a better one?

This is my current setup. There is currently more space at the bottom and a bit to the side since I just recently switched from this RTX3080 FTW3 to a gigabyte 9070XT and the card is overall smaller and shorter

https://preview.redd.it/1pi8nay2zqlh1.jpg?width=1600&format=pjpg&auto=webp&s=c123153bde2002b4ab549b0e7bf4ca0793343420

reddit.com
u/Blackirean — 2 months ago

[Loved Trope] Character obtains unlimited power, uses it for good once and proceeds to reject it knowing its too much for them

Rapunzel's Tangled Adventure: In the show's finale after Rapunzel defeats and destroys the sorceress Zhan Tiri she finally reunites both sides of the ultimate power, her sun drop and the moonstone Opal. With the power at hand she proceeds to heal and revive all her fallen comrades and friends before casting away the power, sending it back to the cosmos.

Amphibia: After attempting to stop the moon from crashing into Amphibia using the powers of the calamity stones Anne, Sasha and Marcy realize they can't do it as Sasha and Marcy's bodies are not accustomed to wielding their powers as much as Anne's is. Anne decides to take the three stones herself and blasts the moon apart, with the power destroying her body as a result. After awakening in limbo, revived by the guardian of the stones, she's given the chance to become the next guardian due to being the only being in millennia to obtain unlimited power and use it for good, but she rejects it due to still being too young and still having a lot of growing left to do. The guardian accepts and sends her back, promising she will try again at the end of Anne's natural life.

Danny Phantom: After tricking and defeating the villain Freakshow and taking the reality gauntlet for himself Danny uses it to fix all the reality warping Freakshow did beforehand, sends him and the other ghosts to the authorities and wipes his secret identity from the memories of his friends, loved ones and everyone else in the world. Once everything is said and done, realizing the dangerous potential of such power Danny decides against keeping it and destroys the gauntlet for good so nobody wields such dangerous artifact again.

u/Blackirean — 2 months ago

How to fix drop down menus?

I'm currently using this version of Material Fox on version 154 for Linux ubuntu. I modified the custom.css file so I could fix some stuff I didn't like but I'm encountering 2 problems.

  1. The drop down menus on webpages are too dark and I want to change the text color. So all my drop downs work well but when I have a drop down inside a webpage, the background is so dark that I can only see the option list when I hover over an option

How it currently looks

And these are all my other menus, I want it to match to this:

Desired result

  1. I want floating tabs. I modified the material fox tabs so they would be floating, by adding curves to the bottom of the rectangle. It worked up until the latest update last week. Now the css stopped working for the tabs and they look like this:

Current Look of active tab

When the mouse hovers over the active tab, the tab becomes shorter like this:

mouse over active tab

And when you hover the mouse on an inactive tab:

Mouse over inactive tab

This is more or less what I want, ideally I would want the tab rounded square to be a bit taller than this but I would settle for this size if the corners are rounded and the tab is floating.

Below is my current custom.css script:

:root,
html,
body {
    /* add your css variables below */

}
/*? taller navbar */
 not (-moz-bool-pref: "shimmer.shorter-navbar") {
#navigator-toolbox, #nav-bar {
min-height: 50px !important;
}

}
/* use variables when about:config variable userChrome.system-accent-colors is true */
 -moz-pref("userChrome.theme-system-accent") {
  /* use variables when dark mode */
   (prefers-color-scheme: dark) {
    :root,
    html,
    body {
      --md-accent-color: color-mix(in srgb, var(--md-white) 15%, AccentColor);
      --md-tab-background-color-hover: color-mix(
        in srgb,
        transparent 75%,
        var(--md-accent-color)
      );
      --md-tab-opacity: 0.9;
      --md-background-color-0: color-mix(
        in srgb,
        var(--md-grey-900) 85%,
        var(--md-accent-color)
      );
      --md-background-color-50: color-mix(
        in srgb,
        var(--md-grey-900-white-4-percent) 85%,
        var(--md-accent-color)
      );
      --md-background-color-100: color-mix(
        in srgb,
        var(--md-grey-900-white-5-percent) 85%,
        var(--md-accent-color)
      );
      --md-menu-background-color-hover: color-mix(
        in srgb,
        var(--md-grey-800) 85%,
        var(--md-accent-color)
      );
      --md-menu-border-color: color-mix(
        in srgb,
        var(--md-grey-800) 85%,
        var(--md-accent-color)
      );
      --md-text-on-accent: var(--md-white);
      --md-secondary-button-text: var(--md-white);
      --md-content-separator-color: color-mix(
        in srgb,
        #606164 90%,
        var(--md-accent-color)
      );
      --md-selection-text-color: var(--md-text-on-accent);
      --md-selection-background-color: var(--md-accent-color);
    }
  }

  /* use variables when light mode */
  u/media (prefers-color-scheme: light) {
    :root,
    html,
    body {
      --md-accent-color: color-mix(
        in srgb,
        var(--md-grey-900) 15%,
        AccentColor
      );
      --md-tab-background-color-hover: color-mix(
        in srgb,
        transparent 75%,
        var(--md-accent-color)
      );
      --md-tab-opacity: 0.9;
      --md-background-color-0: color-mix(
        in srgb,
        #dee1e6 90%,
        var(--md-accent-color)
      );
      --md-background-color-50: color-mix(
        in srgb,
        var(--md-grey-100) 90%,
        var(--md-accent-color)
      );
      --md-background-color-100: color-mix(
        in srgb,
        var(--md-white) 90%,
        var(--md-accent-color)
      );
      --md-menu-background-color-hover: color-mix(
        in srgb,
        var(--md-background-color-0) 90%,
        var(--md-accent-color)
      );
      --md-menu-border-color: color-mix(
        in srgb,
        var(--md-background-color-0) 90%,
        var(--md-accent-color)
      );
      --md-content-separator-color: color-mix(
        in srgb,
        #dbdcdd 90%,
        var(--md-accent-color)
      );
      --md-secondary-button-text: var(--md-accent-color);
      --md-selection-text-color: var(--md-text-on-accent);
      --md-selection-background-color: var(--md-accent-color);
    }
  }
}
/* add your user css below */

/* hide grouped tabs */
.tabbrowser-tab[aria-hidden="true"] {
    display: none !important;
}

/* smaller group icon */
/*.tab-group-label {
    min-height: 25px !important;
    line-height: 1.2 !important;
    min-width: 40px !important; */
}

/* lighter color and no border on group icon */
tab-group[collapsed] .tab-group-label-container .tab-group-label {
    background-color: light-dark(var(--tab-group-color), var(--tab-group-color-invert)) !important;
    color: light-dark(var(--tab-group-color), var(--tab-group-color-invert)) !important;
    outline: none !important;
    box-shadow: none !important;
}

/* add border under grouped tabs */
vbox.tab-group-label-container:has(label[aria-expanded="true"])~tab {
    border-bottom: 2px solid light-dark(var(--tab-group-color), var(--tab-group-color-invert)) !important;
    transform: translateY(-1px) !important;
}

/* remove broken hover border */
vbox.tab-group-label-container:has(label[aria-expanded="true"])~tab:hover .tab-group-line {
    background-color: transparent !important;
    opacity: 0 !important;
}

/* add left/right/top border for selected group elements */
vbox.tab-group-label-container:has(label[aria-expanded="true"])~tab[selected="true"] .tab-background {
    border: 2px solid light-dark(var(--tab-group-color), var(--tab-group-color-invert)) !important;
    border-bottom: none !important;
    border-radius: 12px 12px 0 0 !important;
}

/* remove the bottom border for selected tabs */
vbox.tab-group-label-container:has(label[aria-expanded="true"])~tab[selected="true"] {
    border-bottom: none !important;
    transform: none !important;
}

vbox.tab-group-label-container:has(label[aria-expanded="true"])~tab[selected="true"] .tab-group-line {
    background-color: transparent !important;
    opacity: 0 !important;
}
.tabbrowser-tab[selected="true"] .tab-background {
  margin-top: 4px !important;
  margin-bottom: 2px !important;
  border-radius: 15px !important;
 &:before,
      &:after {
        content: none !important;
}

}
reddit.com
u/Blackirean — 2 months ago
▲ 6 r/Kubuntu+2 crossposts

Help getting 9070xt to work on Kubuntu 26.04 LTS

I recently upgraded my GPU from an Nvidia 30 series to a 9070xt Gigabyte OC that I got for a good price.

However, after installing and connecting everything I ran into a problem when booting to Linux, 30 seconds to a minute after login in I get a green screen in my main display and a black screen on my secondary display and I am forced to reboot my computer. I'm using a 4K 144Hz monitor via HDMI 2.1 and a 1440p 144Hz monitor via DP 1.4

I'm pretty sure that the card itself is alright, I managed to boot into my windows 10 install on a separate drive, installed the drivers and I was able to run Cyberpunk and Expedition 33 at 4K high with no crashes, this only happens on Linux.

I used Gemini to try and navigate through some of the potential issues. Before anything I also uninstalled the Nvidia driver 595 for my 30 Series card. My motherboard is using the latest BIOS available. I am using a B850I aorus pro with a 9700x CPU and 48GB of DDR5 RAM

First I tried lowering the speed of my PCIex16 port to Gen4 instead of Gen5, didn't work

I tried installing the AMD drivers for ubuntu directly from the website, didn't work

Tried booting with only 1 display (the 4k HDMI). Didn't work and my main 4K display got the green screen again

Tried changing the cable and port of the 4k screen to DP to not use HDMI 2.1, didn't work but now I got a gray screen instead.

Tried lowering the refresh rate of both displays to 60Hz before the crash happens. This one managed to slow down the crash, the computer worked normally for about 2 minutes before giving me the gray screen again.

Has anyone experienced something similar? Is it my distro? something specific about my configuration? the 9070xt?

I'm running KDE plasma 6.6.6

KDE frameworks 6.24.0

Qt version 6.10.2

Kernel version: 7.0.0-30-generic

Wayland

And according to glxinfo I am using Mesa 26.0.3, although running apt-cache policy libgl1-mesa-dri says Installed: 26.0.3-1ubuntu1 Candidate: 26.0.8-1ubuntu0.3

I keep all my packages up to date.

Please help

reddit.com
u/Blackirean — 2 months ago

Any steps I should take before upgrading from a 30 series RTX card to a 9070xt?

I'm using Kubunutu 26.04LTS in a dual boot configuration with Windows 10 in a separate drive. I'm upgrading my GPU since I got a pretty great deal on a 9070xt and it's basically the last card I can get with triple 8 pin connectors. I'd rather avoid the 12 pin for as long as I can, plus I got custom sleeved cables I don't want to replace or hide behind an adapter.

For windows I know I should use DDU to remove my Nvidia drivers before I swap the card, but I wanted to ask if there's any such steps I need to take in my Linux side. I'm fairly new to the distro so I don't know all the details on how these things work.

I'm running a 9700x, 48GB DDR5, NVME SSD and a 850W gold PSU.

reddit.com
u/Blackirean — 2 months ago
🔥 Hot ▲ 7.6k r/TopCharacterTropes

[Funny Trope] Against their deepest wishes, they keep winning

Catch-22: Yossarian is a pilot who above anything wants to not die in the war. To that extent he does everything in his power to sabotage his missions. Only for his superiors to recontextualize many of his actions into those of heroism, thus preventing him from leaving his position much to his disappointment.

The world of otome games is tough for mobs: Leon Bartfort is a man who reincarnated in the world of a dating sim game he played on behalf of his sister, restarting his life from a young age. Using his knowledge he planned to acquire riches and powers to get his own island, and live in peace alone away from the war, conflict and drama he knew would happen this world. But every action he took was nothing but another achievement that added complexity to his plans. Obtain riches? He gets titles that make his family send him to the main school of the story, obtain advance weaponry? forced to fight in a war, then decorated and tasked with even more responsibility, want nothing to do with the plot? get the main girl, the villainess, the protagonist of the sequel and even the Queen to fall in love with him.

Uncle Mo (real life example): An old man in china who has a small shop where he made chicken pot mixed with medicinal herbs he manually harvested from the mountains. Problem is that vloggers made their way to his tiny shop and caused it to explode in popularity. Suddenly he's inundated with customers, sometimes hundreds of them, who wait hours in line to have his food. The shop is small and managed only with him and sometimes family members so managing that amount of customers is arduous, even obtaining the ingredients has become a chore. He has openly protested against the vloggers trying to bring more fame to his shop, has shared the recipe openly for everyone to make, claimed that the food will make people feel bad or that their stool would be loose. All to no avail, as his popularity grew even more and even the government of his province forbade him from stopping as the business and attention his shop brings are good for the province.

u/Blackirean — 2 months ago
▲ 4 r/linuxhardware+1 crossposts

Bluetooth Controller not working after BIOS update

So I'm running Kubuntu 26.04 and recently updated my motherboard's BIOS. I'm using the Gigabyte B850i Aorus pro Rev 1.0 motherboard. Before the update everything worked fine but after installing the latest BIOS available from the website my game controller no longer works when connected via Bluetooth. It works when connected using a cable so I know the controller is not the problem.

I tried everything. From unisntalling and resintalling xpadneo, to running sudo rfkill unblock bluetooth and even doing a full power down by disconnecting my PC PSU. Can anybody help me fix this?

My PC has a R7 9700x, 48G of DDR5 and as mentioned the motherboard is the Gigabyte B850i Aorus Pro Rev 1.0.

Please help

Edit: Fixed the problem. Turns out that after the BIOS update secure boot was turned on in the BIOS without me noticing. I turned it off and the controller works perfectly.

I Was about to completely reinstall Kubuntu until I decided to give a try to get xpadneo working. When I tried to manually get it started I got an error. After googling, it turned out that the error comes from having secure boot on and it was preventing the xpadneo driver from recognizing the controller input even though it was properly connected via Bluetooth.

reddit.com
u/Blackirean — 2 months ago

Should I upgrade to 5070ti or 9070xt?

Recently decided to switch to Linux after windows 10 died and windows 11 was enshittified. After some distro hopping I ended with kubuntu for long term support, stability and KDE plasma. Maybe thinking on trying fedora KDE later on if I feel like I'm missing on some bleeding edge stuff but that's not the point.

I managed to refresh my PC last year before prices went insane, new CPU, MOBO, 48GB of RAM, 14TB HDD and 4TB SSD. The only thing that didn't move was my trusty EVGA RTX 3080 10GB I've had since late 2020 and my 850W gold PSU.

I've been thinking of finally upgrading the card to compensate for some of the loss in performance that comes with Linux, and my options are to go for the 5070ti or the 9070XT.

I will admit I've only ever used Nvidia cards, I'm used to the software and was an overall fan. I've never tried any radeon cards so I don't know what I'm getting into or what features I might be giving up if I switch cards. Codecs, resolution, features, no idea I was a plug and play guy that simply updated game ready drivers.

I know the 9070xt is almost on par with the 5070ti, with the exception of ray tracing. And from the looks of it much better price wise. After some cashback, selling old hardware and the such I can basically get one for about 360$ (and can finance those to about 120 a month for 3 months no interest), whereas I would have to shell about twice that much for a 5070ti especially since I would need an SFF version to fit in my case. I would also have to rely on the infamous 12pin cable, while the 9070xt would still allow me to keep the 3 8pins (and as a bonus I can keep using my custom sleeved 8pin cables)

I don't know if the extra money would be worth it in the long term with the 5070ti.

Am I being too paranoid about abandoning Nvidia? Is there any unseen downsides or better, benefits to using an AMD card?

My specs are:

Ryzen 7 9700x

Gigabyte Aorus B850I Pro

48GB DDR5 6400 MT/s

Samsung 970 EVO Plus 2TB

Samsung 990 Pro 4TB

EVGA RTX 3080 FTW3 10GB LHR

Cooler Master V850 SFX

reddit.com
u/Blackirean — 2 months ago
🔥 Hot ▲ 9.4k r/TopCharacterTropes

[Rare trope] Discriminating, but for an actually good reason

Frieren, Beyond Journey's end: In the anime/manga the eponymous Frieren is an Elf who's spent the better part of a millennia learning magic specifically to kill demons. She's learned countless spells, tricks and even spent most of her life suppressing her power all for the purpose of killing them. At first we simply see that humanity is at war with them, and even question if her crusade against demon-kind is justified, after all it's hard to antagonize an entire sentient species. But no, demons are very much a species to be feared; they are not normal animals or creatures, they are monsters (shown by how they disintegrate when they die), they are predators that evolved speech for the sole purpose of tricking and catching humans off guard, are naturally extremely powerful and possess magic that humanity often can't use. While they feel emotions to some extent, their way of thinking is so fundamentally incompatible with humanity's mindset that the series shows many mages with the ability to read minds struggle or find it painful and harmful to try and read a demon's thoughts. Tools that are meant to control them fail not because they're faulty but because simple order such as "hold no ill will or commit no malicious acts" don't compute the same way in their brains, as what we consider evil is normal for them.

Marvel Comics: In a run of the Storm solo series, she visits an underground superhuman hospital run by Night nurse and Doctor Daye, a mutant with the power to diagnose any illness. When she requires treatment she is in turn reject by the doctor, she of course assumes this is blatant discrimination to which the doctor agrees. Storm assumes the doctor refuses to treat mutants, something they are very much used to in Marvel. But he ends up revealing that about 80% of everyone he treats are mutants, and that he simply refuses to treat any member of the X-men. Why? because his special hospital was built via several donations by the superhero community. From the Avengers, to the Defenders, Fantastic 4, heroes for hire. Hell, even Moonknight of all people contributed to the hospital. But the X-men never once gave anything to it, even though they preach the safety of mutant-kind and such a hospital is essential for those who can't seek medical treatment out of fear of prosecution.

u/Blackirean — 2 months ago
🔥 Hot ▲ 11.7k r/TopCharacterTropes

Character sacrifices themselves because they know the consequences won't be as dire for them

Avengers Endgame: After the time heist and regathering the infinity stones, the team assembles a new infinity gauntlet. When it is time to make another snap they try to decide who'll take the gauntlet and the possibly deadly feedback. In the end, Banner in the form of professor Hulk decides to be the one, saying that much of the energy out of the gauntlet is gamma, similar to what's empowering. That plus his durability makes him the most likely to survive the snap. And later we see this to be the case when Tony uses them and ends up far worse.

Malcolm in the Middle: In a cold open, Dewey and Hal are trying to get rid of a spider. When things get out of control and Hal begins to frantically try to not let it go, they desperately run towards the door. When they throw the spider out, it is revealed that Lois was outside and the spider lands on her. Dewey proceeds to close the door immediately and tell Hal to run, saying that the worst she can do is ground him, while Hal will have to face the full strength of her Fury. Hal thanks him before running away in justified panic.

u/Blackirean — 3 months ago

Anybody know how to create custom emojis?

so the new update brings a really nice feature to use letters, some Chinese characters or emojis as the main fingerprint sensor icon. I experimented and ended with this fire symbol in japanese

but I got thinking. if we can add more emojis there's basically no limit on what image we can use as the fingerprint icon.

anybody got any ideas?

u/Blackirean — 3 months ago

How to I fix controller rumble in Skul the hero slayer (non-steam)

So I have an instance of the game Skul the hero slayer. The game has a native linux version, I run it from a folder by opening a run.sh file.

Problem is that rumble seems to not work at all. No matter the setting I choose.

I'm using an xbox elite controller 2, in Kubuntu 26.04. I have xpadneo installed and I have been able to use rumble in any other game I launch from steam, just not this one. Maybe cause I launch it directly instead of through steam?

I need some help please.

reddit.com
u/Blackirean — 3 months ago

Command to bring app to focus and/or open it?

I'm trying to create some widget shortcuts for apps and I'm having problems finding a command to focus/maximize firefox if I have it open, or open a new instance if I don't have firefox open.

Looking online I found these main ways:

wmctrl -xa firefox || firefox

wmctrl -xa firefox || firefox&

xdotool search --onlyvisible --class firefox  windowactivate ||firefox

Neither of these three was able to achieve what I was looking for, none brought minimized firefox to focus. They either not work or they simply open a whole new Firefox window. Regardless if I have another one open.

I am running the default version of firefox that comes with Kubuntu LTS. What can I do?

reddit.com
u/Blackirean — 3 months ago

Made a quick script to create draggable honeycomb icons

As the title explains I made this script to create some honeycombs since I missed having them and rainmeter doesn't work on linux. they can be dragged, freely resized, launch custom apps or commands and they remember their position after the script is killed or the computer turned off. Left clicking any gives you the options to edit the config file to add more honeycombs, open the folder to make more icons, refresh the layout after adding or removing instances, reset the honeycombs to the middle of the screen to rearrange or straight up stop the script and kill them.

Below are the instructions to implement it, I am planning on maybe putting it on github with ready made downloadable files but first I'm trying to get it to work on KDE since I no longer use mint and switched to Kubuntu. In the config code below I have the honeycombs to launch the terminal, open the download folder and either launch firefox or bring it to focus if it's already open.

The script was tested in a linux mint cinammon VM. It may work better or worse depending on the computer I don't know. If anyone finds any problems please let me know, I would appreciate the feedback.

There are a couple of known bugs, first is that if you click a hexagon to open an app or run a command and quickly try to click a different hexagon that is next to it, it might launch the first honeycomb command instead of the new one. Waiting for a little bit before clicking is usually the best or maybe keep them separated.

You can see it in action here:

https://imgur.com/a/IwXJAOH

Linux Mint Cinnamon: Floating Pointy-Top Honeycomb Launcher Guide:

# 1. System Dependencies

Open your terminal and run this command to install the required GTK3 and Cairo libraries:

sudo apt update && sudo apt install python3-gi gir1.2-gtk-3.0 python3-cairo -y

# 2. Directory Layout

For the script to run cleanly, create this folder structure in your Home directory:

/home/YOUR_USERNAME/Honeycomb/

├── honeycomb.py

├── honeycomb_config.json

└── icons/

├── steam_hex.png

├── firefox_hex.png

└── discord_hex.png

Place your honeycomb png or ico files here int he icons folder. You can get them form google or make your own, but they have to be here. I only had the discord, rainmeter and firefox icons so that's what you see in the gif above.

# 3. Configuration File (honeycomb_config.json)

Create this file inside the Honeycomb folder. Add or remove hexagons by editing this JSON layout. Replace yourusername with your actual Linux Mint username.

[
  {
    "id": "steam",
    "image_path": "/home/yourusername/Honeycomb/icons/steam_hex.png",
    "command": "gnome-terminal",
    "size": 120
  },
  {
    "id": "firefox",
    "image_path": "/home/yourusername/Honeycomb/icons/firefox_hex.png",
    "command": "wmctrl -a firefox || firefox &",
    "size": 120
  },
  {
    "id": "discord",
    "image_path": "/home/yourusername/Honeycomb/icons/discord_hex.png",
    "command": "xdg-open /home/osboxes/Downloads",
    "size": 120
  }
]

# 4. The Python Script (honeycomb.py)

Create hoenycomb.py inside the Honeycomb folder. Paste the following code block into it:

#!/usr/bin/env python3
import sys
import os
import subprocess
import math
import json
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Gdk, GdkPixbuf, GLib
import cairo

LAYOUT_DIR = os.path.expanduser("~/.config/hex_launchers")
LAYOUT_FILE = os.path.join(LAYOUT_DIR, "positions.json")

ACTIVE_WIDGETS = []
IMAGE_CACHE = {}
MENU_PROTOTYPE = None

class HexagonalWidget(Gtk.Window):
    _slots_ = ('widget_id', 'image_path', 'command', 'height', 'width', 
                 'drag_start_x', 'drag_start_y', 'has_moved', 'pixbuf')
    
    def _init_(self, config, saved_pos):
        super()._init_(type=Gtk.WindowType.TOPLEVEL)
        
        self.widget_id = config["id"]
        self.image_path = config["image_path"]
        self.command = config["command"]
        self.height = config.get("size", 120)
        self.width = int(self.height * (math.sqrt(3) / 2))
        
        self.set_title("")
        self.set_decorated(False)
        self.set_app_paintable(True)
        self.set_keep_above(True)
        self.set_skip_taskbar_hint(True)
        self.set_skip_pager_hint(True)
        self.set_type_hint(Gdk.WindowTypeHint.DESKTOP)
        self.set_default_size(self.width, self.height)
        
        screen = self.get_screen()
        visual = screen.get_rgba_visual()
        if visual and screen.is_composited():
            self.set_visual(visual)
        
        self.drag_start_x = 0
        self.drag_start_y = 0
        self.has_moved = False
        
        self.connect("draw", HexagonalWidget.on_draw)
        self.connect("button-press-event", HexagonalWidget.on_button_press)
        self.connect("button-release-event", HexagonalWidget.on_button_release)
        self.connect("motion-notify-event", HexagonalWidget.on_motion_notify)
        self.connect("realize", HexagonalWidget.on_realize)
        self.connect("destroy", HexagonalWidget.on_destroy)
        
        self.pixbuf = IMAGE_CACHE.get(self.image_path)
        if not self.pixbuf and os.path.exists(self.image_path):
            try:
                self.pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale(
                    self.image_path, self.width, self.height, True
                )
                IMAGE_CACHE[self.image_path] = self.pixbuf
            except Exception:
                self.pixbuf = None

        if saved_pos and self.widget_id in saved_pos:
            pos = saved_pos[self.widget_id]
            self.move(pos['x'], pos['y'])
        else:
            self.move(200, 200)

        ACTIVE_WIDGETS.append(self)
        self.show_all()

    u/staticmethod
    def draw_pointy_hexagon(cr, width, height):
        center_x = width / 2
        center_y = height / 2
        radius = height / 2
        cos_60 = math.sqrt(3) / 2
        
        cr.move_to(center_x + radius * cos_60, center_y - radius)
        for angle in (math.pi/6, math.pi/2, 5*math.pi/6, 7*math.pi/6, 3*math.pi/2):
            cr.line_to(center_x + radius * cos_60 * math.cos(angle), 
                      center_y + radius * math.sin(angle) / cos_60)

    u/staticmethod
    def on_realize(widget):
        width, height = widget.width, widget.height
        surface = cairo.ImageSurface(cairo.FORMAT_A1, width, height)
        cr = cairo.Context(surface)
        HexagonalWidget.draw_pointy_hexagon(cr, width, height)
        cr.set_source_rgba(1, 1, 1, 1)
        cr.fill()
        
        region = Gdk.cairo_region_create_from_surface(surface)
        widget.get_window().input_shape_combine_region(region, 0, 0)

    u/staticmethod
    def on_draw(widget, cr):
        cr.set_source_rgba(0, 0, 0, 0)
        cr.set_operator(cairo.OPERATOR_SOURCE)
        cr.paint()
        cr.set_operator(cairo.OPERATOR_OVER)
        
        if widget.pixbuf:
            Gdk.cairo_set_source_pixbuf(cr, widget.pixbuf, 0, 0)
            cr.paint()
        else:
            HexagonalWidget.draw_pointy_hexagon(cr, widget.width, widget.height)
            cr.set_source_rgba(0.2, 0.6, 0.8, 0.8)
            cr.fill()

    u/staticmethod
    def on_button_press(widget, event):
        if event.button == 1:
            widget.drag_start_x, widget.drag_start_y = event.x, event.y
            widget.has_moved = False
        elif event.button == 3:
            widget.show_context_menu(event)
        else:
            return False
        return True

    def show_context_menu(self, event):
        global MENU_PROTOTYPE
        if not MENU_PROTOTYPE:
            MENU_PROTOTYPE = Gtk.Menu()
            items = [
                ("Edit Cluster", self.on_edit_triggered),
                ("Reset Cluster", self.on_reset_triggered),
                ("Open Folder", self.on_open_folder_triggered),
                ("Refresh Layout", self.on_refresh_triggered),
                ("Open Script", self.on_open_script_triggered),
                ("Stop Script", self.on_stop_triggered)
            ]
            for label, callback in items:
                item = Gtk.MenuItem(label=label)
                item.connect("activate", callback)
                MENU_PROTOTYPE.append(item)
            MENU_PROTOTYPE.show_all()
        
        MENU_PROTOTYPE.popup_at_pointer(event)

    u/staticmethod
    def on_refresh_triggered(menu_item):
        global ACTIVE_WIDGETS
        for widget in ACTIVE_WIDGETS[:]:
            if widget:
                widget.destroy()
        ACTIVE_WIDGETS.clear()
        
        script_dir = os.path.dirname(os.path.abspath(_file_))
        config_file = os.path.join(script_dir, "honeycomb_config.json")
        
        try:
            with open(config_file, 'r') as f:
                widgets_config = json.load(f)
            saved_positions = load_all_positions()
            for config in widgets_config:
                HexagonalWidget(config, saved_positions)
        except Exception as e:
            print(f"Error refreshing: {e}")

    u/staticmethod
    def on_reset_triggered(menu_item):
        if not ACTIVE_WIDGETS:
            return
            
        widget = next(w for w in ACTIVE_WIDGETS if w)
        display = Gdk.Display.get_default()
        monitor = display.get_monitor_at_window(widget.get_window())
        geometry = monitor.get_geometry()
        
        center_x = geometry.width // 2
        center_y = geometry.height // 2
        total_widgets = len(ACTIVE_WIDGETS)
        spacing = 15
        
        total_width = (total_widgets * widget.width) + ((total_widgets - 1) * spacing)
        start_x = center_x - (total_width // 2)
        
        for idx, widget in enumerate(ACTIVE_WIDGETS):
            new_x = start_x + (idx * (widget.width + spacing))
            new_y = center_y - (widget.height // 2)
            widget.move(new_x, new_y)
            global_save_position(widget.widget_id, (new_x, new_y))

    u/staticmethod
    def on_stop_triggered(menu_item):
        Gtk.main_quit()

    u/staticmethod
    def on_open_script_triggered(menu_item):
        script_file = os.path.join(os.path.dirname(os.path.abspath(_file_)), "honeycomb.py")
        subprocess.Popen(['xdg-open', script_file], close_fds=True)

    u/staticmethod
    def on_edit_triggered(menu_item):
        config_file = os.path.join(os.path.dirname(os.path.abspath(_file_)), "honeycomb_config.json")
        subprocess.Popen(['xdg-open', config_file], close_fds=True)

    u/staticmethod
    def on_open_folder_triggered(menu_item):
        folder_path = os.path.dirname(os.path.abspath(_file_))
        subprocess.Popen(['xdg-open', folder_path], close_fds=True)

    u/staticmethod
    def on_motion_notify(widget, event):
        if event.state & Gdk.ModifierType.BUTTON1_MASK:
            widget.has_moved = True
            widget.move(int(event.x_root - widget.drag_start_x),
                       int(event.y_root - widget.drag_start_y))
            return True
        return False

    u/staticmethod
    def on_button_release(widget, event):
        if event.button == 1:
            if not widget.has_moved:
                try:
                    subprocess.Popen(widget.command, shell=True, close_fds=True)
                except Exception as e:
                    print(f"[{widget.widget_id}] Launch failed: {e}")
            else:
                widget.has_moved = False
                global_save_position(widget.widget_id, widget.get_position())
            return True
        return False

    u/staticmethod
    def on_destroy(widget):
        try:
            ACTIVE_WIDGETS.remove(widget)
        except ValueError:
            pass

def load_all_positions():
    try:
        if os.path.exists(LAYOUT_FILE):
            with open(LAYOUT_FILE, 'r') as f:
                return json.load(f)
    except Exception:
        pass
    return {}

def global_save_position(widget_id, coords):
    try:
        os.makedirs(LAYOUT_DIR, exist_ok=True)
        positions = load_all_positions()
        positions[widget_id] = {'x': int(coords[0]), 'y': int(coords[1])}
        with open(LAYOUT_FILE, 'w') as f:
            json.dump(positions, f)
    except Exception as e:
        print(f"Failed to save positions: {e}")

if _name_ == "_main_":
    script_dir = os.path.dirname(os.path.abspath(_file_))
    config_file = os.path.join(script_dir, "honeycomb_config.json")
    
    if not os.path.exists(config_file):
        print(f"Error: Config file not found at {config_file}")
        sys.exit(1)
    
    try:
        with open(config_file, 'r') as f:
            widgets_config = json.load(f)
        
        saved_positions = load_all_positions()
        
        for config in widgets_config:
            HexagonalWidget(config, saved_positions)
        
        Gtk.main()
    except KeyboardInterrupt:
        pass
    except Exception as e:
        print(f"Fatal error: {e}")

# 5. Implementation Steps

  1. Make the script executable by running this in your terminal:chmod +x ~/Honeycomb/honeycomb.py
  2. Run it to test:~/Honeycomb/honeycomb.py

# 6. How to Use Features

* *Drag and Drop:* Hold Left-Click on any hexagon to reposition it. Release to save its position permanently.

* *Launch:* Single Left-Click on any hexagon to open the game/app you defined in teh config file.

* *Un-Clutter Feature:* Right-Click any hexagon and select "Reset Cluster" to snap all elements into a row in the center of your monitor.

## 7. Enable Auto-Start on Boot

  1. Open the Menu and launch "Startup Applications".
  2. Click the "+" icon and choose "Custom Command".
  3. Name: Honeycomb Launcher (or whatever you want)
  4. Command: python3 /home/yourusername/Honeycomb/honeycomb.py
  5. Startup Delay: Set to 3 seconds (to let Mint's desktop graphics manager finish rendering transparency before loading the hexagons)
  6. Click Save.
i.redd.it
u/Blackirean — 3 months ago

Made a quick script to create draggable honeycomb icons

As the title explains I made this script to create some honeycombs since I missed having them and rainmeter doesn't work on linux. they can be dragged, freely resized, launch custom apps or commands and they remember their position after the script is killed or the computer turned off. Left clicking any gives you the options to edit the config file to add more honeycombs, open the folder to make more icons, refresh the layout after adding or removing instances, reset the honeycombs to the middle of the screen to rearrange or straight up stop the script and kill them.

Below are the instructions to implement it, I am planning on maybe putting it on github with ready made downloadable files but first I'm trying to get it to work on KDE since I no longer use mint and switched to Kubuntu. In the config code below I have the honeycombs to launch the terminal, open the download folder and either launch firefox or bring it to focus if it's already open.

The script was tested in a linux mint cinammon VM. It may work better or worse depending on the computer I don't know. If anyone finds any problems please let me know, I would appreciate the feedback.

There are a couple of known bugs, first is that if you click a hexagon to open an app or run a command and quickly try to click a different hexagon that is next to it, it might launch the first honeycomb command instead of the new one. Waiting for a little bit before clicking is usually the best or maybe keep them separated.

Linux Mint Cinnamon: Floating Pointy-Top Honeycomb Launcher Guide:

# 1. System Dependencies

Open your terminal and run this command to install the required GTK3 and Cairo libraries:

sudo apt update && sudo apt install python3-gi gir1.2-gtk-3.0 python3-cairo -y

# 2. Directory Layout

For the script to run cleanly, create this folder structure in your Home directory:

/home/YOUR_USERNAME/Honeycomb/

├── honeycomb.py

├── honeycomb_config.json

└── icons/

├── steam_hex.png

├── firefox_hex.png

└── discord_hex.png

Place your honeycomb png or ico files here int he icons folder. You can get them form google or make your own, but they have to be here. I only had the discord, rainmeter and firefox icons so that's what you see in the gif above.

# 3. Configuration File (honeycomb_config.json)

Create this file inside the Honeycomb folder. Add or remove hexagons by editing this JSON layout. Replace yourusername with your actual Linux Mint username.

[
  {
    "id": "steam",
    "image_path": "/home/yourusername/Honeycomb/icons/steam_hex.png",
    "command": "gnome-terminal",
    "size": 120
  },
  {
    "id": "firefox",
    "image_path": "/home/yourusername/Honeycomb/icons/firefox_hex.png",
    "command": "wmctrl -a firefox || firefox &",
    "size": 120
  },
  {
    "id": "discord",
    "image_path": "/home/yourusername/Honeycomb/icons/discord_hex.png",
    "command": "xdg-open /home/osboxes/Downloads",
    "size": 120
  }
]

# 4. The Python Script (honeycomb.py)

Create hoenycomb.py inside the Honeycomb folder. Paste the following code block into it:

#!/usr/bin/env python3
import sys
import os
import subprocess
import math
import json
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Gdk, GdkPixbuf, GLib
import cairo

LAYOUT_DIR = os.path.expanduser("~/.config/hex_launchers")
LAYOUT_FILE = os.path.join(LAYOUT_DIR, "positions.json")

ACTIVE_WIDGETS = []
IMAGE_CACHE = {}
MENU_PROTOTYPE = None

class HexagonalWidget(Gtk.Window):
    _slots_ = ('widget_id', 'image_path', 'command', 'height', 'width', 
                 'drag_start_x', 'drag_start_y', 'has_moved', 'pixbuf')
    
    def _init_(self, config, saved_pos):
        super()._init_(type=Gtk.WindowType.TOPLEVEL)
        
        self.widget_id = config["id"]
        self.image_path = config["image_path"]
        self.command = config["command"]
        self.height = config.get("size", 120)
        self.width = int(self.height * (math.sqrt(3) / 2))
        
        self.set_title("")
        self.set_decorated(False)
        self.set_app_paintable(True)
        self.set_keep_above(True)
        self.set_skip_taskbar_hint(True)
        self.set_skip_pager_hint(True)
        self.set_type_hint(Gdk.WindowTypeHint.DESKTOP)
        self.set_default_size(self.width, self.height)
        
        screen = self.get_screen()
        visual = screen.get_rgba_visual()
        if visual and screen.is_composited():
            self.set_visual(visual)
        
        self.drag_start_x = 0
        self.drag_start_y = 0
        self.has_moved = False
        
        self.connect("draw", HexagonalWidget.on_draw)
        self.connect("button-press-event", HexagonalWidget.on_button_press)
        self.connect("button-release-event", HexagonalWidget.on_button_release)
        self.connect("motion-notify-event", HexagonalWidget.on_motion_notify)
        self.connect("realize", HexagonalWidget.on_realize)
        self.connect("destroy", HexagonalWidget.on_destroy)
        
        self.pixbuf = IMAGE_CACHE.get(self.image_path)
        if not self.pixbuf and os.path.exists(self.image_path):
            try:
                self.pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale(
                    self.image_path, self.width, self.height, True
                )
                IMAGE_CACHE[self.image_path] = self.pixbuf
            except Exception:
                self.pixbuf = None

        if saved_pos and self.widget_id in saved_pos:
            pos = saved_pos[self.widget_id]
            self.move(pos['x'], pos['y'])
        else:
            self.move(200, 200)

        ACTIVE_WIDGETS.append(self)
        self.show_all()

    u/staticmethod
    def draw_pointy_hexagon(cr, width, height):
        center_x = width / 2
        center_y = height / 2
        radius = height / 2
        cos_60 = math.sqrt(3) / 2
        
        cr.move_to(center_x + radius * cos_60, center_y - radius)
        for angle in (math.pi/6, math.pi/2, 5*math.pi/6, 7*math.pi/6, 3*math.pi/2):
            cr.line_to(center_x + radius * cos_60 * math.cos(angle), 
                      center_y + radius * math.sin(angle) / cos_60)

    u/staticmethod
    def on_realize(widget):
        width, height = widget.width, widget.height
        surface = cairo.ImageSurface(cairo.FORMAT_A1, width, height)
        cr = cairo.Context(surface)
        HexagonalWidget.draw_pointy_hexagon(cr, width, height)
        cr.set_source_rgba(1, 1, 1, 1)
        cr.fill()
        
        region = Gdk.cairo_region_create_from_surface(surface)
        widget.get_window().input_shape_combine_region(region, 0, 0)

    u/staticmethod
    def on_draw(widget, cr):
        cr.set_source_rgba(0, 0, 0, 0)
        cr.set_operator(cairo.OPERATOR_SOURCE)
        cr.paint()
        cr.set_operator(cairo.OPERATOR_OVER)
        
        if widget.pixbuf:
            Gdk.cairo_set_source_pixbuf(cr, widget.pixbuf, 0, 0)
            cr.paint()
        else:
            HexagonalWidget.draw_pointy_hexagon(cr, widget.width, widget.height)
            cr.set_source_rgba(0.2, 0.6, 0.8, 0.8)
            cr.fill()

    u/staticmethod
    def on_button_press(widget, event):
        if event.button == 1:
            widget.drag_start_x, widget.drag_start_y = event.x, event.y
            widget.has_moved = False
        elif event.button == 3:
            widget.show_context_menu(event)
        else:
            return False
        return True

    def show_context_menu(self, event):
        global MENU_PROTOTYPE
        if not MENU_PROTOTYPE:
            MENU_PROTOTYPE = Gtk.Menu()
            items = [
                ("Edit Cluster", self.on_edit_triggered),
                ("Reset Cluster", self.on_reset_triggered),
                ("Open Folder", self.on_open_folder_triggered),
                ("Refresh Layout", self.on_refresh_triggered),
                ("Open Script", self.on_open_script_triggered),
                ("Stop Script", self.on_stop_triggered)
            ]
            for label, callback in items:
                item = Gtk.MenuItem(label=label)
                item.connect("activate", callback)
                MENU_PROTOTYPE.append(item)
            MENU_PROTOTYPE.show_all()
        
        MENU_PROTOTYPE.popup_at_pointer(event)

    u/staticmethod
    def on_refresh_triggered(menu_item):
        global ACTIVE_WIDGETS
        for widget in ACTIVE_WIDGETS[:]:
            if widget:
                widget.destroy()
        ACTIVE_WIDGETS.clear()
        
        script_dir = os.path.dirname(os.path.abspath(_file_))
        config_file = os.path.join(script_dir, "honeycomb_config.json")
        
        try:
            with open(config_file, 'r') as f:
                widgets_config = json.load(f)
            saved_positions = load_all_positions()
            for config in widgets_config:
                HexagonalWidget(config, saved_positions)
        except Exception as e:
            print(f"Error refreshing: {e}")

    u/staticmethod
    def on_reset_triggered(menu_item):
        if not ACTIVE_WIDGETS:
            return
            
        widget = next(w for w in ACTIVE_WIDGETS if w)
        display = Gdk.Display.get_default()
        monitor = display.get_monitor_at_window(widget.get_window())
        geometry = monitor.get_geometry()
        
        center_x = geometry.width // 2
        center_y = geometry.height // 2
        total_widgets = len(ACTIVE_WIDGETS)
        spacing = 15
        
        total_width = (total_widgets * widget.width) + ((total_widgets - 1) * spacing)
        start_x = center_x - (total_width // 2)
        
        for idx, widget in enumerate(ACTIVE_WIDGETS):
            new_x = start_x + (idx * (widget.width + spacing))
            new_y = center_y - (widget.height // 2)
            widget.move(new_x, new_y)
            global_save_position(widget.widget_id, (new_x, new_y))

    u/staticmethod
    def on_stop_triggered(menu_item):
        Gtk.main_quit()

    u/staticmethod
    def on_open_script_triggered(menu_item):
        script_file = os.path.join(os.path.dirname(os.path.abspath(_file_)), "honeycomb.py")
        subprocess.Popen(['xdg-open', script_file], close_fds=True)

    u/staticmethod
    def on_edit_triggered(menu_item):
        config_file = os.path.join(os.path.dirname(os.path.abspath(_file_)), "honeycomb_config.json")
        subprocess.Popen(['xdg-open', config_file], close_fds=True)

    u/staticmethod
    def on_open_folder_triggered(menu_item):
        folder_path = os.path.dirname(os.path.abspath(_file_))
        subprocess.Popen(['xdg-open', folder_path], close_fds=True)

    u/staticmethod
    def on_motion_notify(widget, event):
        if event.state & Gdk.ModifierType.BUTTON1_MASK:
            widget.has_moved = True
            widget.move(int(event.x_root - widget.drag_start_x),
                       int(event.y_root - widget.drag_start_y))
            return True
        return False

    u/staticmethod
    def on_button_release(widget, event):
        if event.button == 1:
            if not widget.has_moved:
                try:
                    subprocess.Popen(widget.command, shell=True, close_fds=True)
                except Exception as e:
                    print(f"[{widget.widget_id}] Launch failed: {e}")
            else:
                widget.has_moved = False
                global_save_position(widget.widget_id, widget.get_position())
            return True
        return False

    u/staticmethod
    def on_destroy(widget):
        try:
            ACTIVE_WIDGETS.remove(widget)
        except ValueError:
            pass

def load_all_positions():
    try:
        if os.path.exists(LAYOUT_FILE):
            with open(LAYOUT_FILE, 'r') as f:
                return json.load(f)
    except Exception:
        pass
    return {}

def global_save_position(widget_id, coords):
    try:
        os.makedirs(LAYOUT_DIR, exist_ok=True)
        positions = load_all_positions()
        positions[widget_id] = {'x': int(coords[0]), 'y': int(coords[1])}
        with open(LAYOUT_FILE, 'w') as f:
            json.dump(positions, f)
    except Exception as e:
        print(f"Failed to save positions: {e}")

if _name_ == "_main_":
    script_dir = os.path.dirname(os.path.abspath(_file_))
    config_file = os.path.join(script_dir, "honeycomb_config.json")
    
    if not os.path.exists(config_file):
        print(f"Error: Config file not found at {config_file}")
        sys.exit(1)
    
    try:
        with open(config_file, 'r') as f:
            widgets_config = json.load(f)
        
        saved_positions = load_all_positions()
        
        for config in widgets_config:
            HexagonalWidget(config, saved_positions)
        
        Gtk.main()
    except KeyboardInterrupt:
        pass
    except Exception as e:
        print(f"Fatal error: {e}")

# 5. Implementation Steps

  1. Make the script executable by running this in your terminal:

    chmod +x ~/Honeycomb/honeycomb.py

  2. Run it to test:

    ~/Honeycomb/honeycomb.py

# 6. How to Use Features

* *Drag and Drop:* Hold Left-Click on any hexagon to reposition it. Release to save its position permanently.

* *Launch:* Single Left-Click on any hexagon to open the game/app you defined in teh config file.

* *Un-Clutter Feature:* Right-Click any hexagon and select "Reset Cluster" to snap all elements into a row in the center of your monitor.

## 7. Enable Auto-Start on Boot

  1. Open the Menu and launch "Startup Applications".

  2. Click the "+" icon and choose "Custom Command".

  3. Name: Honeycomb Launcher (or whatever you want)

  4. Command: python3 /home/yourusername/Honeycomb/honeycomb.py

  5. Startup Delay: Set to 3 seconds (to let Mint's desktop graphics manager finish rendering transparency before loading the hexagons)

  6. Click Save.

u/Blackirean — 3 months ago